Spaces:
No application file
No application file
| """ | |
| Scoring & Hiring Decision + Results Dashboard - BEST OF BOTH VERSION | |
| ONLY accurate metrics, NO fake scores | |
| Includes: filler words, improved content similarity, grammar error count | |
| Excludes: eye contact (removed), fake pronunciation, wrong tempo | |
| """ | |
| import streamlit as st | |
| import numpy as np | |
| import pandas as pd | |
| import os | |
| import time | |
| class ScoringDashboard: | |
| """Handles scoring, hiring decisions, and results visualization - ACCURATE ONLY""" | |
| def __init__(self): | |
| """Initialize scoring dashboard""" | |
| pass | |
| def is_valid_transcript(self, text): | |
| """Check if transcript is valid""" | |
| if not text or not text.strip(): | |
| return False | |
| invalid_markers = ["[Could not understand audio]", "[Speech recognition service unavailable]", | |
| "[Error", "[No audio]", "Audio not clear"] | |
| return not any(marker in text for marker in invalid_markers) | |
| def decide_hire(self, result): | |
| """ | |
| Make hiring decision - ACCURATE METRICS ONLY | |
| Uses real, verified measurements | |
| """ | |
| reasons = [] | |
| conf = result.get("emotion_scores", {}).get("confidence", 0) | |
| nerv = result.get("emotion_scores", {}).get("nervousness", 0) | |
| acc = result.get("accuracy", 0) or 0 | |
| flu = result.get("fluency", 0) or 0 | |
| fluency_level = result.get("fluency_level", "No Data") | |
| violations = result.get("violations", []) | |
| fluency_detailed = result.get("fluency_detailed", {}) | |
| speech_rate = fluency_detailed.get("speech_rate", 0) | |
| speech_rate_normalized = fluency_detailed.get("speech_rate_normalized", 0) | |
| grammar_score = fluency_detailed.get("grammar_score", 0) | |
| grammar_errors = fluency_detailed.get("grammar_errors", 0) | |
| lexical_diversity = fluency_detailed.get("lexical_diversity", 0) | |
| coherence_score = fluency_detailed.get("coherence_score", 0) | |
| filler_count = fluency_detailed.get("filler_count", 0) | |
| filler_ratio = fluency_detailed.get("filler_ratio", 0) | |
| pause_ratio = fluency_detailed.get("pause_ratio", 0) | |
| num_pauses = fluency_detailed.get("num_pauses", 0) | |
| has_valid_answer = self.is_valid_transcript(result.get("transcript", "")) | |
| # Check for no valid response | |
| if not has_valid_answer: | |
| return "β No Valid Response", [ | |
| "β No valid audio response detected", | |
| "β οΈ Please ensure you speak clearly during recording" | |
| ] | |
| # Check for violations | |
| if len(violations) > 0: | |
| reasons.append(f"β οΈ {len(violations)} violation(s) detected - under review") | |
| # Calculate positive score | |
| pos = 0 | |
| # === CONFIDENCE === | |
| if conf >= 75: | |
| pos += 2.5 | |
| reasons.append(f"β Excellent confidence ({conf}%)") | |
| elif conf >= 60: | |
| pos += 2 | |
| reasons.append(f"β High confidence ({conf}%)") | |
| elif conf >= 45: | |
| pos += 1 | |
| reasons.append(f"β Moderate confidence ({conf}%)") | |
| else: | |
| reasons.append(f"β οΈ Low confidence ({conf}%)") | |
| # === ANSWER ACCURACY (improved with content similarity) === | |
| if acc >= 75: | |
| pos += 3 | |
| reasons.append(f"β Excellent answer relevance ({acc}%)") | |
| elif acc >= 60: | |
| pos += 2 | |
| reasons.append(f"β Strong answer relevance ({acc}%)") | |
| elif acc >= 45: | |
| pos += 1 | |
| reasons.append(f"β Acceptable answer ({acc}%)") | |
| else: | |
| reasons.append(f"β οΈ Low answer relevance ({acc}%)") | |
| # === FLUENCY === | |
| if fluency_level == "Excellent": | |
| pos += 4 | |
| reasons.append(f"β Outstanding fluency ({flu}% - {fluency_level})") | |
| elif fluency_level == "Fluent": | |
| pos += 3 | |
| reasons.append(f"β Strong fluency ({flu}% - {fluency_level})") | |
| elif fluency_level == "Moderate": | |
| pos += 1.5 | |
| reasons.append(f"β Moderate fluency ({flu}% - {fluency_level})") | |
| else: | |
| reasons.append(f"β οΈ Fluency needs improvement ({flu}% - {fluency_level})") | |
| # === SPEECH RATE === | |
| if speech_rate_normalized >= 0.9: | |
| reasons.append(f"β Optimal speech rate ({speech_rate:.0f} WPM)") | |
| elif speech_rate_normalized >= 0.7: | |
| reasons.append(f"β Good speech rate ({speech_rate:.0f} WPM)") | |
| elif speech_rate > 180: | |
| reasons.append(f"β οΈ Speaking too fast ({speech_rate:.0f} WPM - may indicate nervousness)") | |
| elif speech_rate < 120: | |
| reasons.append(f"β οΈ Speaking too slow ({speech_rate:.0f} WPM)") | |
| # === GRAMMAR === | |
| if grammar_score >= 85: | |
| pos += 1 | |
| reasons.append(f"β Excellent grammar ({grammar_score:.0f}% - {grammar_errors} errors)") | |
| elif grammar_score >= 70: | |
| reasons.append(f"β Good grammar ({grammar_score:.0f}% - {grammar_errors} errors)") | |
| elif grammar_score >= 55: | |
| reasons.append(f"β Acceptable grammar ({grammar_score:.0f}% - {grammar_errors} errors)") | |
| else: | |
| reasons.append(f"β οΈ Grammar needs improvement ({grammar_score:.0f}% - {grammar_errors} errors)") | |
| # === VOCABULARY === | |
| if lexical_diversity >= 65: | |
| pos += 1 | |
| reasons.append(f"β Rich vocabulary ({lexical_diversity:.0f}%)") | |
| elif lexical_diversity >= 50: | |
| reasons.append(f"β Good vocabulary variety ({lexical_diversity:.0f}%)") | |
| else: | |
| reasons.append(f"β οΈ Limited vocabulary ({lexical_diversity:.0f}%)") | |
| # === COHERENCE === | |
| if coherence_score >= 75: | |
| pos += 0.5 | |
| reasons.append(f"β Highly coherent response ({coherence_score:.0f}%)") | |
| elif coherence_score >= 60: | |
| reasons.append(f"β Coherent response ({coherence_score:.0f}%)") | |
| # === FILLER WORDS (NEW - ACCURATE) === | |
| if filler_count == 0: | |
| pos += 0.5 | |
| reasons.append(f"β No filler words detected") | |
| elif filler_count <= 2: | |
| reasons.append(f"β Minimal filler words ({filler_count})") | |
| elif filler_count <= 5: | |
| reasons.append(f"β οΈ Some filler words ({filler_count})") | |
| else: | |
| pos -= 0.5 | |
| reasons.append(f"β οΈ Excessive filler words ({filler_count} - impacts fluency)") | |
| # === PAUSES === | |
| if pause_ratio < 0.15: | |
| reasons.append(f"β Good speech flow ({pause_ratio*100:.1f}% pauses)") | |
| elif pause_ratio < 0.25: | |
| reasons.append(f"β Acceptable pauses ({pause_ratio*100:.1f}%)") | |
| else: | |
| reasons.append(f"β οΈ Frequent pauses ({pause_ratio*100:.1f}% - may indicate hesitation)") | |
| # === NERVOUSNESS PENALTY === | |
| if nerv >= 60: | |
| pos -= 1.5 | |
| reasons.append(f"β οΈ Very high nervousness ({nerv}%)") | |
| elif nerv >= 45: | |
| pos -= 0.5 | |
| reasons.append(f"β οΈ High nervousness ({nerv}%)") | |
| # === VIOLATION PENALTY === | |
| if len(violations) > 0: | |
| violation_penalty = len(violations) * 1.5 | |
| pos -= violation_penalty | |
| # === FINAL DECISION === | |
| if len(violations) >= 3: | |
| decision = "β Disqualified" | |
| reasons.insert(0, "π« Multiple serious violations - integrity compromised") | |
| elif pos >= 9: | |
| decision = "β Strong Hire" | |
| reasons.insert(0, "π― Exceptional candidate - outstanding communication and competence") | |
| elif pos >= 7: | |
| decision = "β Hire" | |
| reasons.insert(0, "π Strong candidate with excellent communication skills") | |
| elif pos >= 5: | |
| decision = "β οΈ Maybe" | |
| reasons.insert(0, "π€ Moderate potential - further evaluation recommended") | |
| elif pos >= 3: | |
| decision = "β οΈ Weak Maybe" | |
| reasons.insert(0, "π Below average - significant concerns present") | |
| else: | |
| decision = "β No" | |
| reasons.insert(0, "β Not recommended - needs substantial improvement") | |
| return decision, reasons | |
| def display_violation_images(self, violations): | |
| """Display violation images""" | |
| if not violations: | |
| return | |
| st.markdown("### π¨ Violation Evidence") | |
| for idx, violation in enumerate(violations): | |
| violation_reason = violation.get('reason', 'Unknown violation') | |
| violation_time = violation.get('timestamp', 0) | |
| image_path = violation.get('image_path') | |
| col1, col2 = st.columns([2, 3]) | |
| with col1: | |
| if image_path and os.path.exists(image_path): | |
| st.image(image_path, caption=f"Violation #{idx+1}", use_container_width=True) | |
| else: | |
| st.error("Image not available") | |
| with col2: | |
| st.markdown(f""" | |
| **Violation #{idx+1}** | |
| - **Type:** {violation_reason} | |
| - **Time:** {violation_time:.1f}s into question | |
| - **Status:** β οΈ Flagged for review | |
| """) | |
| if idx < len(violations) - 1: | |
| st.markdown("---") | |
| def display_immediate_results(self, result): | |
| """Display immediate results - ACCURATE METRICS ONLY""" | |
| st.markdown("---") | |
| st.subheader("π Question Results") | |
| # Show accuracy badge | |
| improvements = result.get("improvements_applied", {}) | |
| if improvements.get('no_fake_metrics'): | |
| st.success("β **All metrics verified accurate** - No fake scores included") | |
| col_v, col_r = st.columns([2, 3]) | |
| with col_v: | |
| if os.path.exists(result.get('video_path', '')): | |
| st.video(result['video_path']) | |
| with col_r: | |
| # Show violations | |
| violations = result.get('violations', []) | |
| if violations: | |
| st.error(f"β οΈ **{len(violations)} Violation(s) Detected**") | |
| with st.expander("View Violation Evidence", expanded=True): | |
| self.display_violation_images(violations) | |
| st.write("**π Transcript:**") | |
| if self.is_valid_transcript(result.get('transcript', '')): | |
| st.text_area("", result['transcript'], height=100, disabled=True, label_visibility="collapsed") | |
| else: | |
| st.error(result.get('transcript', 'No transcript')) | |
| # Main metrics (4 columns - NO fake metrics) | |
| m1, m2, m3, m4 = st.columns(4) | |
| with m1: | |
| st.metric("π Confidence", f"{result.get('emotion_scores', {}).get('confidence', 0)}%") | |
| with m2: | |
| st.metric("π Accuracy", f"{result.get('accuracy', 0)}%", | |
| help="Content similarity to ideal answer") | |
| with m3: | |
| fluency_level = result.get('fluency_level', 'N/A') | |
| st.metric("π£οΈ Fluency", f"{result.get('fluency', 0)}%", delta=fluency_level) | |
| with m4: | |
| filler_count = result.get('filler_count', 0) | |
| filler_status = "β " if filler_count <= 2 else "β οΈ" | |
| st.metric(f"{filler_status} Filler Words", filler_count, | |
| help="um, uh, like, etc.") | |
| # Enhanced fluency breakdown | |
| fluency_detailed = result.get('fluency_detailed', {}) | |
| if fluency_detailed: | |
| st.markdown("---") | |
| st.markdown("**π Detailed Fluency Analysis (All Accurate):**") | |
| fc1, fc2, fc3, fc4 = st.columns(4) | |
| with fc1: | |
| speech_rate = fluency_detailed.get('speech_rate', 0) | |
| speech_rate_norm = fluency_detailed.get('speech_rate_normalized', 0) | |
| ideal = "β " if speech_rate_norm >= 0.9 else ("β" if speech_rate_norm >= 0.7 else "β οΈ") | |
| st.metric(f"{ideal} Speech Rate", f"{speech_rate:.0f} WPM", | |
| delta=f"Quality: {speech_rate_norm:.2f}") | |
| with fc2: | |
| pause_ratio = fluency_detailed.get('pause_ratio', 0) | |
| num_pauses = fluency_detailed.get('num_pauses', 0) | |
| pause_status = "β " if pause_ratio < 0.2 else ("β" if pause_ratio < 0.3 else "β οΈ") | |
| st.metric(f"{pause_status} Pauses", f"{num_pauses}", | |
| delta=f"{pause_ratio*100:.1f}% time") | |
| with fc3: | |
| grammar = fluency_detailed.get('grammar_score', 0) | |
| errors = fluency_detailed.get('grammar_errors', 0) | |
| grammar_status = "β " if grammar >= 85 else ("β" if grammar >= 70 else "β οΈ") | |
| st.metric(f"{grammar_status} Grammar", f"{grammar:.0f}%", | |
| delta=f"{errors} errors") | |
| with fc4: | |
| diversity = fluency_detailed.get('lexical_diversity', 0) | |
| div_status = "β " if diversity >= 65 else ("β" if diversity >= 50 else "β οΈ") | |
| st.metric(f"{div_status} Vocabulary", f"{diversity:.0f}%", | |
| help="Unique meaningful words") | |
| # Additional metrics | |
| st.markdown("**π Additional Metrics:**") | |
| detail_metrics = fluency_detailed.get('detailed_metrics', {}) | |
| col_det1, col_det2, col_det3 = st.columns(3) | |
| with col_det1: | |
| st.write(f"**Coherence:** {fluency_detailed.get('coherence_score', 0):.0f}%") | |
| if improvements.get('bert_coherence'): | |
| st.caption("π§ BERT-enhanced") | |
| st.write(f"**Avg Pause:** {fluency_detailed.get('avg_pause_duration', 0):.2f}s") | |
| with col_det2: | |
| st.write(f"**Total Words:** {detail_metrics.get('total_words', 0)}") | |
| st.write(f"**Meaningful Words:** {detail_metrics.get('meaningful_words', 0)}") | |
| with col_det3: | |
| st.write(f"**Unique Words:** {detail_metrics.get('unique_words', 0)}") | |
| st.write(f"**Filler Ratio:** {fluency_detailed.get('filler_ratio', 0)*100:.1f}%") | |
| st.markdown("---") | |
| decision = result.get('hire_decision', 'N/A') | |
| if "β " in decision: | |
| st.markdown(f'<div class="success-box"><h3>{decision}</h3></div>', unsafe_allow_html=True) | |
| elif "β οΈ" in decision: | |
| st.markdown(f'<div class="warning-box"><h3>{decision}</h3></div>', unsafe_allow_html=True) | |
| else: | |
| st.markdown(f'<div class="error-box"><h3>{decision}</h3></div>', unsafe_allow_html=True) | |
| st.write("**Reasons:**") | |
| for r in result.get('hire_reasons', []): | |
| st.write(f"β’ {r}") | |
| def display_performance_overview(self, results): | |
| """Display performance overview - ACCURATE METRICS ONLY""" | |
| st.subheader("π Performance Overview") | |
| # Count violations | |
| total_violations = sum(len(r.get('violations', [])) for r in results) | |
| questions_with_violations = sum(1 for r in results if len(r.get('violations', [])) > 0) | |
| if total_violations > 0: | |
| st.warning(f"β οΈ **{total_violations} violation(s) detected across {questions_with_violations} question(s)**") | |
| valid_results = [r for r in results if r.get("has_valid_data", False)] | |
| if valid_results: | |
| # Calculate averages | |
| confs = [r.get("emotion_scores", {}).get("confidence", 0) for r in valid_results] | |
| accs = [r.get("accuracy", 0) for r in valid_results] | |
| fluencies = [r.get("fluency", 0) for r in valid_results] | |
| wpms = [r.get("wpm", 0) for r in valid_results] | |
| filler_counts = [r.get("filler_count", 0) for r in valid_results] | |
| # Enhanced metrics | |
| grammar_scores = [r.get("fluency_detailed", {}).get("grammar_score", 0) for r in valid_results] | |
| diversity_scores = [r.get("fluency_detailed", {}).get("lexical_diversity", 0) for r in valid_results] | |
| coherence_scores = [r.get("fluency_detailed", {}).get("coherence_score", 0) for r in valid_results] | |
| pause_ratios = [r.get("fluency_detailed", {}).get("pause_ratio", 0) for r in valid_results] | |
| speech_rate_norms = [r.get("fluency_detailed", {}).get("speech_rate_normalized", 0) for r in valid_results] | |
| avg_conf = np.mean(confs) | |
| avg_acc = np.mean(accs) | |
| avg_flu = np.mean(fluencies) | |
| avg_wpm = np.mean(wpms) | |
| avg_filler = np.mean(filler_counts) | |
| avg_grammar = np.mean(grammar_scores) if grammar_scores else 0 | |
| avg_diversity = np.mean(diversity_scores) if diversity_scores else 0 | |
| avg_coherence = np.mean(coherence_scores) if coherence_scores else 0 | |
| avg_speech_norm = np.mean(speech_rate_norms) if speech_rate_norms else 0 | |
| # Main metrics | |
| m1, m2, m3, m4, m5 = st.columns(5) | |
| with m1: | |
| st.markdown(f"<div class='metric-card'><h3>{avg_conf:.1f}%</h3><p>Avg Confidence</p></div>", unsafe_allow_html=True) | |
| with m2: | |
| st.markdown(f"<div class='metric-card'><h3>{avg_acc:.1f}%</h3><p>Avg Accuracy</p></div>", unsafe_allow_html=True) | |
| with m3: | |
| st.markdown(f"<div class='metric-card'><h3>{avg_flu:.1f}%</h3><p>Avg Fluency</p></div>", unsafe_allow_html=True) | |
| with m4: | |
| filler_status = "β " if avg_filler <= 2 else "β οΈ" | |
| st.markdown(f"<div class='metric-card'><h3>{filler_status} {avg_filler:.1f}</h3><p>Avg Filler Words</p></div>", unsafe_allow_html=True) | |
| with m5: | |
| wpm_status = "β " if 140 <= avg_wpm <= 160 else "β οΈ" | |
| st.markdown(f"<div class='metric-card'><h3>{wpm_status} {avg_wpm:.1f}</h3><p>Avg WPM</p></div>", unsafe_allow_html=True) | |
| # Enhanced fluency breakdown | |
| st.markdown("### π£οΈ Detailed Fluency Breakdown") | |
| st.caption("β All metrics verified accurate - No fake scores") | |
| fm1, fm2, fm3, fm4, fm5 = st.columns(5) | |
| with fm1: | |
| st.markdown(f"<div class='metric-card'><h3>{avg_grammar:.1f}%</h3><p>Grammar βοΈ</p></div>", unsafe_allow_html=True) | |
| with fm2: | |
| st.markdown(f"<div class='metric-card'><h3>{avg_diversity:.1f}%</h3><p>Vocabulary π</p></div>", unsafe_allow_html=True) | |
| with fm3: | |
| st.markdown(f"<div class='metric-card'><h3>{avg_coherence:.1f}%</h3><p>Coherence π</p></div>", unsafe_allow_html=True) | |
| with fm4: | |
| avg_pause = np.mean(pause_ratios) if pause_ratios else 0 | |
| st.markdown(f"<div class='metric-card'><h3>{avg_pause*100:.1f}%</h3><p>Pause Ratio βΈοΈ</p></div>", unsafe_allow_html=True) | |
| with fm5: | |
| norm_status = "β " if avg_speech_norm >= 0.9 else ("β" if avg_speech_norm >= 0.7 else "β οΈ") | |
| st.markdown(f"<div class='metric-card'><h3>{norm_status} {avg_speech_norm:.2f}</h3><p>Speech Quality</p></div>", unsafe_allow_html=True) | |
| # Overall recommendation | |
| st.markdown("---") | |
| st.subheader("π― Overall Recommendation") | |
| if total_violations >= 5: | |
| st.error("β **Disqualified** - Multiple serious violations detected") | |
| st.info("Candidate showed pattern of policy violations during interview") | |
| else: | |
| # ACCURATE weighted scoring | |
| overall_score = ( | |
| avg_conf * 0.15 + # Confidence | |
| avg_acc * 0.25 + # Answer accuracy (improved) | |
| avg_flu * 0.30 + # Overall fluency (accurate) | |
| avg_grammar * 0.10 + # Grammar | |
| avg_diversity * 0.08 + # Vocabulary | |
| avg_coherence * 0.07 + # Coherence | |
| (100 - avg_filler * 10) * 0.05 # Filler penalty | |
| ) | |
| # Violation penalty | |
| violation_penalty = total_violations * 5 | |
| final_score = max(0, overall_score - violation_penalty) | |
| col_rec1, col_rec2 = st.columns([1, 2]) | |
| with col_rec1: | |
| st.metric("Overall Score", f"{final_score:.1f}%", | |
| delta=f"-{violation_penalty}%" if violation_penalty > 0 else None) | |
| with col_rec2: | |
| if total_violations > 0: | |
| st.warning(f"β οΈ Score reduced by {violation_penalty}% due to {total_violations} violation(s)") | |
| if final_score >= 80: | |
| st.success("β **Exceptional Candidate** - Strong hire recommendation") | |
| st.info("Outstanding communication, fluency, and technical competence") | |
| elif final_score >= 70: | |
| st.success("β **Strong Candidate** - Recommended for hire") | |
| st.info("Excellent communication skills with minor areas for growth") | |
| elif final_score >= 60: | |
| st.warning("β οΈ **Moderate Candidate** - Further evaluation recommended") | |
| st.info("Good potential with notable room for improvement") | |
| elif final_score >= 50: | |
| st.warning("β οΈ **Weak Candidate** - Significant concerns") | |
| st.info("Below expectations in multiple areas") | |
| else: | |
| st.error("β **Not Recommended** - Does not meet standards") | |
| st.info("Substantial improvement needed across all metrics") | |
| # Charts | |
| st.markdown("---") | |
| st.subheader("π Detailed Analytics") | |
| col_chart1, col_chart2 = st.columns(2) | |
| with col_chart1: | |
| st.write("**Performance by Question**") | |
| chart_data = pd.DataFrame({ | |
| 'Question': [f"Q{i+1}" for i in range(len(valid_results))], | |
| 'Confidence': confs, | |
| 'Accuracy': accs, | |
| 'Fluency': fluencies | |
| }) | |
| st.line_chart(chart_data.set_index('Question')) | |
| with col_chart2: | |
| st.write("**Fluency Components (Accurate)**") | |
| fluency_breakdown = pd.DataFrame({ | |
| 'Component': ['Grammar', 'Vocabulary', 'Coherence', 'Speech Rate', 'Pauses'], | |
| 'Score': [ | |
| avg_grammar, | |
| avg_diversity, | |
| avg_coherence, | |
| avg_speech_norm * 100, | |
| (1 - np.mean(pause_ratios)) * 100 if pause_ratios else 0 | |
| ] | |
| }) | |
| st.bar_chart(fluency_breakdown.set_index('Component')) | |
| def display_detailed_results(self, results): | |
| """Display detailed question-by-question analysis""" | |
| st.markdown("---") | |
| st.subheader("π Question-by-Question Analysis") | |
| for i, r in enumerate(results): | |
| decision = r.get('hire_decision', 'N/A') | |
| fluency_level = r.get('fluency_level', 'N/A') | |
| violations = r.get('violations', []) | |
| violation_badge = f"β οΈ {len(violations)} violation(s)" if violations else "β Clean" | |
| filler_count = r.get('filler_count', 0) | |
| with st.expander(f"Q{i+1}: {r.get('question', '')[:60]}... β {decision} | {violation_badge} | Fluency: {fluency_level}", expanded=False): | |
| # Display violations | |
| if violations: | |
| st.error(f"**π¨ {len(violations)} Violation(s) Detected**") | |
| self.display_violation_images(violations) | |
| st.markdown("---") | |
| col_vid, col_txt = st.columns([2, 3]) | |
| with col_vid: | |
| if os.path.exists(r.get('video_path', '')): | |
| st.video(r['video_path']) | |
| with col_txt: | |
| st.markdown(f"**π Question:** {r.get('question', '')}") | |
| st.markdown("**π¬ Transcript:**") | |
| if self.is_valid_transcript(r.get('transcript', '')): | |
| st.text_area("", r['transcript'], height=80, disabled=True, key=f"t_{i}", label_visibility="collapsed") | |
| else: | |
| st.error(r.get('transcript', 'No transcript')) | |
| # Main metrics | |
| m1, m2, m3, m4 = st.columns(4) | |
| with m1: | |
| st.metric("π Confidence", f"{r.get('emotion_scores', {}).get('confidence', 0)}%") | |
| st.metric("π Accuracy", f"{r.get('accuracy', 0)}%") | |
| with m2: | |
| st.metric("π° Nervousness", f"{r.get('emotion_scores', {}).get('nervousness', 0)}%") | |
| st.metric("π£οΈ Fluency", f"{r.get('fluency', 0)}%") | |
| with m3: | |
| st.metric("π« Filler Words", filler_count) | |
| st.metric("π΄ Blinks", f"{r.get('blink_count', 0)}") | |
| with m4: | |
| st.metric("π Outfit", r.get('outfit', 'Unknown')) | |
| st.metric("π¬ WPM", f"{r.get('wpm', 0)}") | |
| # Enhanced fluency breakdown | |
| fluency_detailed = r.get('fluency_detailed', {}) | |
| if fluency_detailed: | |
| st.markdown("---") | |
| st.markdown("**π Accurate Fluency Analysis:**") | |
| fcol1, fcol2, fcol3 = st.columns(3) | |
| with fcol1: | |
| st.write(f"**Grammar:** {fluency_detailed.get('grammar_score', 0):.0f}% βοΈ") | |
| st.write(f"**Errors:** {fluency_detailed.get('grammar_errors', 0)}") | |
| st.write(f"**Vocabulary:** {fluency_detailed.get('lexical_diversity', 0):.0f}% π") | |
| with fcol2: | |
| st.write(f"**Coherence:** {fluency_detailed.get('coherence_score', 0):.0f}% π") | |
| st.write(f"**Pauses:** {fluency_detailed.get('num_pauses', 0)}") | |
| st.write(f"**Pause Ratio:** {fluency_detailed.get('pause_ratio', 0)*100:.1f}% βΈοΈ") | |
| with fcol3: | |
| speech_norm = fluency_detailed.get('speech_rate_normalized', 0) | |
| st.write(f"**Speech Quality:** {speech_norm:.2f}") | |
| st.write(f"**Fluency Level:** {r.get('fluency_level', 'N/A')}") | |
| st.write(f"**Filler Ratio:** {fluency_detailed.get('filler_ratio', 0)*100:.1f}%") | |
| # Show detailed word counts | |
| detail_metrics = fluency_detailed.get('detailed_metrics', {}) | |
| if detail_metrics: | |
| st.markdown("**π Word Analysis:**") | |
| st.caption(f"Total: {detail_metrics.get('total_words', 0)} | " | |
| f"Meaningful: {detail_metrics.get('meaningful_words', 0)} | " | |
| f"Unique: {detail_metrics.get('unique_words', 0)} | " | |
| f"Fillers: {detail_metrics.get('filler_words_detected', 0)}") | |
| if detail_metrics.get('stopword_filtered'): | |
| st.caption("β Stopword filtering applied") | |
| st.markdown("---") | |
| st.markdown(f"**Decision:** {decision}") | |
| st.markdown("**Reasons:**") | |
| for reason in r.get('hire_reasons', []): | |
| st.write(f"β’ {reason}") | |
| def export_results_csv(self, results): | |
| """Export results to CSV - ACCURATE METRICS ONLY""" | |
| export_data = [] | |
| for i, r in enumerate(results): | |
| fluency_detailed = r.get('fluency_detailed', {}) | |
| violations = r.get('violations', []) | |
| detail_metrics = fluency_detailed.get('detailed_metrics', {}) | |
| improvements = r.get('improvements_applied', {}) | |
| export_data.append({ | |
| "Question_Number": i + 1, | |
| "Question": r.get("question", ""), | |
| "Transcript": r.get("transcript", ""), | |
| "Violations_Count": len(violations), | |
| "Violation_Details": "; ".join([v['reason'] for v in violations]), | |
| "Confidence": r.get("emotion_scores", {}).get("confidence", 0), | |
| "Nervousness": r.get("emotion_scores", {}).get("nervousness", 0), | |
| "Accuracy": r.get("accuracy", 0), | |
| "Fluency_Score": r.get("fluency", 0), | |
| "Fluency_Level": r.get("fluency_level", ""), | |
| "Speech_Rate_WPM": fluency_detailed.get("speech_rate", 0), | |
| "Speech_Rate_Normalized": fluency_detailed.get("speech_rate_normalized", 0), | |
| "Grammar_Score": fluency_detailed.get("grammar_score", 0), | |
| "Grammar_Errors": fluency_detailed.get("grammar_errors", 0), | |
| "Lexical_Diversity": fluency_detailed.get("lexical_diversity", 0), | |
| "Coherence_Score": fluency_detailed.get("coherence_score", 0), | |
| "Pause_Ratio": fluency_detailed.get("pause_ratio", 0), | |
| "Avg_Pause_Duration": fluency_detailed.get("avg_pause_duration", 0), | |
| "Num_Pauses": fluency_detailed.get("num_pauses", 0), | |
| "Filler_Word_Count": fluency_detailed.get("filler_count", 0), | |
| "Filler_Word_Ratio": fluency_detailed.get("filler_ratio", 0), | |
| "Total_Words": detail_metrics.get("total_words", 0), | |
| "Meaningful_Words": detail_metrics.get("meaningful_words", 0), | |
| "Unique_Words": detail_metrics.get("unique_words", 0), | |
| "Unique_Meaningful_Words": detail_metrics.get("unique_meaningful_words", 0), | |
| "Blink_Count": r.get("blink_count", 0), | |
| "Outfit": r.get("outfit", ""), | |
| "Outfit_Confidence": r.get("outfit_confidence", 0), | |
| "Hire_Decision": r.get("hire_decision", ""), | |
| "Accurate_Metrics_Only": improvements.get("no_fake_metrics", False), | |
| "Stopword_Filtering": improvements.get("stopword_filtering", False), | |
| "Quality_Weighted_Emotions": improvements.get("quality_weighted_emotions", False), | |
| "BERT_Coherence": improvements.get("bert_coherence", False), | |
| "Content_Similarity": improvements.get("content_similarity_matching", False), | |
| "Filler_Word_Detection": improvements.get("filler_word_detection", False) | |
| }) | |
| df = pd.DataFrame(export_data) | |
| csv = df.to_csv(index=False) | |
| return csv | |
| def render_dashboard(self, results): | |
| """Render complete results dashboard - ACCURATE METRICS ONLY""" | |
| if not results: | |
| st.info("π No results yet. Complete some questions first.") | |
| return | |
| # Show accuracy badge | |
| if results: | |
| improvements = results[0].get("improvements_applied", {}) | |
| if improvements.get('no_fake_metrics'): | |
| st.success("β **ALL METRICS VERIFIED ACCURATE** | No fake pronunciation, No wrong tempo scores") | |
| active_improvements = [] | |
| if improvements.get('stopword_filtering'): | |
| active_improvements.append("π Stopword Filtering") | |
| if improvements.get('quality_weighted_emotions'): | |
| active_improvements.append("βοΈ Quality-Weighted Emotions") | |
| if improvements.get('content_similarity_matching'): | |
| active_improvements.append("π Content Similarity") | |
| if improvements.get('bert_coherence'): | |
| active_improvements.append("π§ BERT Coherence") | |
| if improvements.get('filler_word_detection'): | |
| active_improvements.append("π« Filler Word Detection") | |
| if improvements.get('grammar_error_count'): | |
| active_improvements.append("βοΈ Grammar Error Count") | |
| if active_improvements: | |
| st.info("**Real Improvements:** " + " | ".join(active_improvements)) | |
| # Performance overview | |
| self.display_performance_overview(results) | |
| # Detailed results | |
| self.display_detailed_results(results) | |
| # Export option | |
| st.markdown("---") | |
| col_export1, col_export2 = st.columns(2) | |
| with col_export1: | |
| if st.button("π₯ Download Accurate Results as CSV", use_container_width=True): | |
| csv = self.export_results_csv(results) | |
| st.download_button( | |
| "πΎ Download CSV", | |
| csv, | |
| f"interview_results_accurate_{time.strftime('%Y%m%d_%H%M%S')}.csv", | |
| "text/csv", | |
| use_container_width=True | |
| ) | |
| with col_export2: | |
| # Show accuracy details | |
| if st.button("βΉοΈ View Accuracy Details", use_container_width=True): | |
| with st.expander("β Verified Accurate Metrics", expanded=True): | |
| st.markdown(""" | |
| ### β What's ACCURATE (Verified & Kept) | |
| **π£οΈ Fluency & Speech Analysis:** | |
| - β **Speech Rate (WPM)**: Real words per minute calculation | |
| - β **Pause Detection**: Librosa audio analysis (actual silence detection) | |
| - β **Grammar Checking**: language_tool_python (real grammar rules) | |
| - β **Filler Word Count**: Detects "um", "uh", "like", etc. (NEW) | |
| - β **Lexical Diversity**: Stopword-filtered vocabulary richness | |
| - β **Coherence**: BERT semantic analysis or transition word heuristics | |
| **π Answer Quality:** | |
| - β **Semantic Similarity**: SentenceTransformer embeddings | |
| - β **Content Similarity**: difflib SequenceMatcher (IMPROVED) | |
| - β **Keyword Matching**: Honest fallback when needed | |
| **π― Emotional & Visual:** | |
| - β **Quality-Weighted Emotions**: Face size/lighting/centrality weighted | |
| - β **Outfit Analysis**: Multi-criteria color + YOLO classification | |
| --- | |
| ### β What's REMOVED (Fake/Inaccurate) | |
| - β **Fake Pronunciation Score**: Was hardcoded to 90% (not real analysis) | |
| - β **Wrong Tempo-Based Fluency**: Used music beat detection (wrong domain) | |
| - β **Eye Contact in Results**: Removed (still tracked for violations only) | |
| --- | |
| ### π― Why This Matters | |
| **Fake metrics lead to:** | |
| - β Bad hiring decisions | |
| - β Legal liability | |
| - β Loss of trust | |
| - β Unfair candidate evaluation | |
| **Accurate metrics provide:** | |
| - β Fair assessment | |
| - β Defensible decisions | |
| - β Real insights | |
| - β Continuous improvement data | |
| --- | |
| ### π Scoring Formula (Accurate) | |
| ``` | |
| Overall Score = | |
| Confidence Γ 0.15 + | |
| Accuracy Γ 0.25 + (Improved similarity) | |
| Fluency Γ 0.30 + (Real metrics only) | |
| Grammar Γ 0.10 + | |
| Vocabulary Γ 0.08 + | |
| Coherence Γ 0.07 + | |
| (100 - FillerΓ10) Γ 0.05 (NEW penalty) | |
| - Violations Γ 5% | |
| ``` | |
| **All components are REAL and VERIFIED.** | |
| """) | |
| ### |