"""Render CV project matching results in 3-panel tactical layout.""" from __future__ import annotations import html from typing import Any, Dict, List def render_cv_matching_html( cv_skills_data: Dict[str, Any], matched_projects: List[Dict[str, Any]], filename: str = "Unknown" ) -> str: """ Renders CV matching results in a 3-panel tactical layout. Panel 1: CV Analysis Panel 2: Project Matches (ranked) Panel 3: Training Costs per Project Args: cv_skills_data: Extracted CV skills matched_projects: Projects with match data and training plans filename: CV filename Returns: HTML string for display """ # CSS for 3-panel layout css = """ """ # Extract data tech_skills = cv_skills_data.get("technical_skills", []) soft_skills = cv_skills_data.get("soft_skills", []) experience_years = cv_skills_data.get("experience_years", "unknown") summary = cv_skills_data.get("summary", "") total_skills = len(tech_skills) + len(soft_skills) # Build HTML html_parts = [css, '
'] # Header safe_filename = html.escape(filename) html_parts.append(f"""

CV + PROJECT MATCHING REPORT

SOURCE: {safe_filename}
PROJECTS ANALYZED: {len(matched_projects)}
CANDIDATE SKILLS: {total_skills}
""") # 3-Panel Grid html_parts.append('
') # ============== PANEL 1: CV ANALYSIS ============== html_parts.append('
') html_parts.append('
📄 CANDIDATE PROFILE
') # Summary if summary: safe_summary = html.escape(summary) html_parts.append(f'
{safe_summary}
') # Stats html_parts.append(f"""
Experience: {html.escape(str(experience_years))}
Tech Skills: {len(tech_skills)}
Soft Skills: {len(soft_skills)}
""") # Skills if tech_skills: html_parts.append('
💻 TECHNICAL
') for skill in tech_skills[:15]: # Limit display html_parts.append(f'{html.escape(skill)}') if len(tech_skills) > 15: html_parts.append(f'+{len(tech_skills) - 15} more') html_parts.append('
') if soft_skills: html_parts.append('
🤝 SOFT SKILLS
') for skill in soft_skills[:10]: html_parts.append(f'{html.escape(skill)}') if len(soft_skills) > 10: html_parts.append(f'+{len(soft_skills) - 10} more') html_parts.append('
') html_parts.append('
') # Close Panel 1 # ============== PANEL 2: PROJECT MATCHES ============== html_parts.append('
') html_parts.append('
🎯 PROJECT MATCHES (RANKED)
') if matched_projects: for project in matched_projects: match_pct = project['match_percentage'] # Determine match level if match_pct >= 90: match_class = "perfect" elif match_pct >= 70: match_class = "good" elif match_pct >= 50: match_class = "partial" else: match_class = "low" project_name = html.escape(project['project_name']) matched_count = project['matched_skills_count'] required_count = project['required_skills_count'] missing_count = project['missing_skills_count'] status = html.escape(project.get('status', 'Unknown')) # Convert budget to int/float for formatting budget = project.get("budget", 0) try: budget_num = float(budget) if budget else 0 except (ValueError, TypeError): budget_num = 0 html_parts.append(f'
') html_parts.append(f'
{match_pct}%
') html_parts.append(f'
{project_name}
') html_parts.append(f'
STATUS: {status} | BUDGET: ${budget_num:,.0f}
') html_parts.append(f'
') html_parts.append(f'✓ {matched_count} Match') html_parts.append(f'|') html_parts.append(f'✗ {missing_count} Gap') html_parts.append(f'|') html_parts.append(f'{required_count} Required') html_parts.append('
') else: html_parts.append('
No projects found for matching.
') html_parts.append('
') # Close Panel 2 # ============== PANEL 3: SKILL GAPS SUMMARY ============== html_parts.append('
') html_parts.append('
⚠️ SKILL GAPS
') if matched_projects: # Show missing skills for each project has_gaps = False for project in matched_projects: if project['missing_skills_count'] == 0: continue # Skip projects with 100% match has_gaps = True project_name = html.escape(project['project_name']) missing_skills = project.get('missing_skills', []) html_parts.append('
') html_parts.append(f'
{project_name}
') html_parts.append(f'
{project["missing_skills_count"]} skill gap(s)
') # List missing skills html_parts.append('
') for skill in missing_skills[:5]: # Show max 5 skill_name = html.escape(skill) html_parts.append(f'
• {skill_name}
') if len(missing_skills) > 5: html_parts.append(f'
... and {len(missing_skills) - 5} more
') html_parts.append('
') html_parts.append('
') if not has_gaps: html_parts.append('
✅ Candidate is fully qualified for all projects!
No skill gaps detected.
') else: html_parts.append('
No data available.
') html_parts.append('
') # Close Panel 3 html_parts.append('
') # Close panels grid html_parts.append('
') # Close wrapper return ''.join(html_parts)