from __future__ import annotations import math # ruff: noqa: E501 — long HTML/CSS inline style strings are intentional from src.core.models import MatchResult, MatchScores, Profile, Rationale, SearchResultItem def _bullet_years(years: float | None) -> str: return f" \u2022 {years:.0f}yrs exp" if years else "" MATCH_COLORS = { "strong_match": "#10b981", "good_match": "#3b82f6", "potential_match": "#f59e0b", "weak_match": "#ef4444", } _BAR_COLORS = { "Overall": "#b8a9c9", "Skill": "#8ab89e", "Experience": "#9a8ab0", "Semantic": "#a8ccb8", "Keyword": "#ccc09f", "Education": "#ccafb6", "AI Rerank": "#b5c8da", "Behavioral": "#b8929a", "Career": "#b8a87c", "Proficiency": "#b8a9c9", } def _bar_color(label: str) -> str: return _BAR_COLORS.get(label.strip(), "#b8a9c9") def _score_bar(label: str, value: float, color: str | None = None) -> str: pct = max(0, min(100, int(value * 100))) c = color or _bar_color(label) return f"""
{label}
{pct}%
""" def _score_badge(value: float) -> str: pct = int(value * 100) if pct >= 70: css_class = "score-strong" elif pct >= 50: css_class = "score-good" elif pct >= 30: css_class = "score-potential" else: css_class = "score-weak" return f"""
{pct}
{css_class.replace('score-', '')}
""" def create_candidate_card(item: SearchResultItem) -> str: s = item.scores if item.scores is not None else MatchScores() bars = "" bars += _score_bar("Overall", s.overall) bars += _score_bar("Skill", s.skill_match) bars += _score_bar("Experience", s.experience_match) bars += _score_bar("Semantic", s.semantic_similarity) bars += _score_bar("Keyword", s.keyword_match) if s.education_match is not None: bars += _score_bar("Education", s.education_match) if s.cross_encoder_score is not None: bars += _score_bar("AI Rerank", s.cross_encoder_score) if s.behavioral_score is not None: bars += _score_bar("Behavioral", s.behavioral_score) if s.career_trajectory_score is not None: bars += _score_bar("Career", s.career_trajectory_score) if s.skill_proficiency_score is not None: bars += _score_bar("Proficiency", s.skill_proficiency_score) skills_html = "".join( f'{skill}' for skill in item.matched_skills[:8] ) missing_html = "".join( f'{skill}' for skill in item.missing_skills[:5] ) return f"""
#{item.rank} {item.name} {item.profile_id}
{item.current_title or 'N/A'} {( f' at ' f'{item.current_company}' if item.current_company else "" )}
{item.location or 'Location N/A'} {_bullet_years(item.experience_years)}
{_score_badge(s.overall)}
SCORE BREAKDOWN confidence: {s.confidence:.0%}
{bars}
{skills_html} {missing_html}
""" def create_score_radar_chart(scores: dict) -> str: dims = ["Skill", "Experience", "Semantic", "Keyword", "Confidence"] dim_keys = [ "skill_match", "experience_match", "semantic_similarity", "keyword_match", "confidence", ] values = [int(scores.get(k, 0) * 100) for k in dim_keys] cx, cy, r = 100, 100, 80 angles = [math.radians(90 - i * 72) for i in range(5)] outer_points = " ".join( f"{cx + r * math.cos(a):.1f},{cy - r * math.sin(a):.1f}" for a in angles ) data_points = " ".join( f"{cx + v * r / 100 * math.cos(a):.1f},{cy - v * r / 100 * math.sin(a):.1f}" for v, a in zip(values, angles) ) labels = "".join( f'{dim}' for dim, a in zip(dims, angles) ) grid_lines = "" for frac in [0.25, 0.5, 0.75]: pts = " ".join( f"{cx + r * frac * math.cos(a):.1f},{cy - r * frac * math.sin(a):.1f}" for a in angles ) grid_lines += f'' return f""" {grid_lines} {labels} """ def create_skill_match_table(rationale: Rationale) -> str: rows = "" for sd in rationale.skill_details: icon = "\u2705" if sd.found else "\u274c" rows += f"{icon}{sd.skill}{sd.evidence}" return f""" {rows}
Status Skill Evidence
""" def create_analytics_dashboard(results_json: str = "[]") -> str: import json from src.core.models import MatchResult, MatchScores from src.fairness.metrics import ( compute_all_fairness_metrics, ) if not results_json or results_json.strip() in ("[]", "", "{}"): return create_empty_analytics() try: raw = json.loads(results_json) if results_json else [] except (json.JSONDecodeError, TypeError): raw = [] total = len(raw) listwise_ranked = any(r.get("listwise_ranked", False) for r in raw) if raw else False listwise_badge = '🏆 Listwise Ranked' if listwise_ranked else "" match_results = [] for r in raw: if isinstance(r, dict): scores_dict = r.get("scores", {}) if not isinstance(scores_dict, dict): scores_dict = {} match_scores = MatchScores( overall=float(scores_dict.get("overall", r.get("_re_score", 0)) or 0), semantic_similarity=float(scores_dict.get("semantic_similarity") or 0), keyword_match=float(scores_dict.get("keyword_match") or 0), skill_match=float(scores_dict.get("skill_match", 0) or 0), experience_match=float(scores_dict.get("experience_match", 0) or 0), location_match=float(scores_dict.get("location_match") or 0) if scores_dict.get("location_match") is not None else None, education_match=float(scores_dict.get("education_match") or 0) if scores_dict.get("education_match") is not None else None, confidence=float(scores_dict.get("confidence", 0) or 0), ) match_results.append( MatchResult( query_id="", profile_id=r.get("profile_id", ""), rank=r.get("rank", 1), name=r.get("name", ""), scores=match_scores, matched_skills=r.get("matched_skills", []), missing_skills=r.get("missing_skills", []), ) ) scores = [m.scores.overall for m in match_results if m.scores.overall > 0] if scores: avg_score = sum(scores) / len(scores) max_score = max(scores) min_score = min(scores) bins = [0] * 10 for s in scores: idx = min(9, int(s * 10)) bins[idx] += 1 max_bin = max(bins) or 1 pastel_colors = [ "#fbcfe8", "#fed7aa", "#fde68a", "#a7f3d0", "#bfdbfe", "#c4b5fd", "#ddd6fe", "#fbcfe8", "#fed7aa", "#a7f3d0" ] bar_chart = "".join( f'
' for i, b in enumerate(bins) ) else: avg_score = max_score = min_score = 0 bar_chart = '
No scores to display
' metric_cards = "" if len(match_results) >= 3: profiledict = _get_bias_profiles(match_results) fairness = compute_all_fairness_metrics(match_results, profiledict) dp = fairness.get("demographic_parity", {}) lang_bias = fairness.get("language_bias", {}) def _metric_card(label, value, threshold, format_str="{:.3f}"): val = value if isinstance(value, (int, float)) else 0 if val < threshold: cls = "pastel-green" color = "#059669" status = "✅ No bias detected" elif val < threshold * 2: cls = "pastel-amber" color = "#d97706" status = "👀 Monitor closely" else: cls = "pastel-rose" color = "#e11d48" status = "⚠️ Bias detected" return f"""
{label}
{format_str.format(val)}
{status}
""" metric_cards = _metric_card("University Parity", dp.get("university", 1.0), 0.8) metric_cards += _metric_card("City Parity", dp.get("city", 1.0), 0.8) metric_cards += _metric_card("Language Parity", dp.get("language", 1.0), 0.8) metric_cards += _metric_card( "Language Avg Rank Diff", abs(lang_bias.get("rank_diff", 0)), 2.0, "{:.1f} ranks", ) return f"""

Fairness & Bias Metrics

{listwise_badge} 🔒 PII Anonymized {total} candidates
{metric_cards or '
Run a search to see metrics
'}

Score Distribution

{bar_chart}
0%50%100%
Avg: {avg_score:.1%} Max: {max_score:.1%} Min: {min_score:.1%}
{_build_distribution_table(match_results)}
""" def _build_distribution_table(match_results: list[MatchResult]) -> str: strong = sum(1 for m in match_results if m.scores.overall >= 0.8) good = sum(1 for m in match_results if 0.6 <= m.scores.overall < 0.8) potential = sum(1 for m in match_results if 0.4 <= m.scores.overall < 0.6) weak = sum(1 for m in match_results if m.scores.overall < 0.4) total = len(match_results) or 1 return f"""
{strong}
Strong ({strong*100//total}%)
{good}
Good ({good*100//total}%)
{potential}
Potential ({potential*100//total}%)
{weak}
Weak ({weak*100//total}%)
""" def _get_bias_profiles(match_results: list[MatchResult]) -> dict[str, Profile]: """Build minimal Profile objects for bias detection from match results.""" from src.core.models import Location, PersonalInfo, Profile, ProfileMetadata return { m.profile_id: Profile( profile_id=m.profile_id, personal=PersonalInfo( name=m.name or "", location=Location(city=m.location), languages_spoken=[], ), metadata=ProfileMetadata(language_detected="en"), ) for m in match_results } def create_rationale_panel(rationale: Rationale | None, profile_summary: str) -> str: if rationale is None: return "" color = MATCH_COLORS.get(rationale.recommendation.value, "#a78bfa") summary = rationale.summary or "No summary available." strengths_html = "".join( f"
  • {s}
  • " for s in rationale.strengths[:5] ) gaps_html = "".join( f"
  • {g}
  • " for g in rationale.gaps[:5] ) return f"""

    Rationale: {profile_summary}

    {summary}

    ✓ Strengths
      {strengths_html}
    ✗ Gaps
      {gaps_html}
    {rationale.recommendation.value}
    """ # ── Progressive Loading Steps ────────────────────────────────────────── LOADING_STEPS = [ ("🔍", "Parsing query", "Understanding job requirements, skills, and context"), ("📡", "Searching index", "Scanning 100K+ profiles with hybrid search"), ("⚡", "AI reranking", "Cross-encoder scoring for precision matching"), ("📊", "Computing scores", "Multi-signal evaluation across 6 dimensions"), ("🎯", "Building results", "Assembling ranked shortlist with rationales"), ] LOADING_STEP_TIMING = [0.15, 0.40, 0.60, 0.80, 1.0] def _step_class(i: int, current_step: int) -> str: if current_step < 0: return "completed" if i < current_step: return "completed" if i == current_step: return "active" return "" def _step_extra(i: int, current_step: int, desc: str) -> str: if i == current_step and 0 <= current_step < len(LOADING_STEPS): return f'
    {desc}' return "" def create_progress_html(current_step: int = 0) -> str: if current_step < 0: steps_html = "".join( f'
    ' f'{icon}{label}
    ' for icon, label, _ in LOADING_STEPS ) pct = 100 else: steps_html = "".join( f'
    ' f'{icon}{label}' f'{_step_extra(i, current_step, desc)}' f'
    ' for i, (icon, label, desc) in enumerate(LOADING_STEPS) ) pct = int(sum(LOADING_STEP_TIMING[:current_step + 1]) / len(LOADING_STEPS) * 100) step_label = LOADING_STEPS[current_step][1] if 0 <= current_step < len(LOADING_STEPS) else "Complete" step_desc = LOADING_STEPS[current_step][2] if 0 <= current_step < len(LOADING_STEPS) else "" icon = "✅" if current_step < 0 else "⏳" return f"""
    {icon} {step_label}
    {step_desc}
    {steps_html}
    """ def create_loading_overlay(message: str = "Searching candidates...") -> str: return f"""\
    {message}
    This may take 10-30 seconds for deep search
    """ def create_empty_state() -> str: return """\
    🔍
    Ready to find talent
    Describe the ideal candidate on the left and click Search Candidates to find matching profiles.
    Adjust scoring sliders in the sidebar to fine-tune results
    """ def create_error_panel(message: str) -> str: """Return a prominent error message panel for display in the UI.""" return f"""\
    ⚠️ Error

    {message}

    """ def create_empty_analytics() -> str: return """\
    📊
    No results yet
    Run a search first to see analytics and fairness metrics.
    """