#!/usr/bin/env python3
"""
Comprehensive Report Generator
Creates detailed human-readable HTML reports with all video insights.
"""
import json
from pathlib import Path
from typing import Dict, List, Any
from datetime import datetime
from video_content_analyzer import VideoContentAnalyzer
class ComprehensiveReportGenerator:
"""Generate comprehensive HTML reports for video analysis."""
def __init__(self):
self.analyzer = VideoContentAnalyzer()
def _get_html_template(self) -> str:
"""Get modern HTML template with dark theme."""
return '''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title}</title>
<style>
/* OKLCH-based dark theme with APCA contrast optimization */
:root {{
/* High contrast OKLCH neutrals for accessibility */
--bg: oklch(0.11 0.015 250); /* Deep dark background */
--surface: oklch(0.16 0.02 250); /* Card surfaces */
--surface-2: oklch(0.19 0.022 250); /* Alternate surfaces */
--surface-3: oklch(0.22 0.025 250); /* Hover states */
/* APCA-optimized text colors */
--text: oklch(0.95 0.008 250); /* Primary text - high contrast */
--text-secondary: oklch(0.82 0.012 250); /* Secondary text */
--muted: oklch(0.62 0.018 250); /* Muted text */
/* High contrast accent colors using OKLCH */
--accent: oklch(0.70 0.18 240); /* Blue accent */
--accent-2: oklch(0.72 0.16 160); /* Green accent */
--accent-3: oklch(0.78 0.15 70); /* Amber accent */
--accent-4: oklch(0.68 0.20 20); /* Red accent */
--accent-5: oklch(0.70 0.18 280); /* Purple accent */
/* APCA-optimized border colors */
--border: oklch(0.28 0.02 250); /* Subtle borders */
--border-strong: oklch(0.35 0.025 250); /* Strong borders */
/* Design tokens */
--radius: 16px;
--radius-sm: 8px;
--shadow: 0 4px 6px -1px oklch(0.0 0.0 0 / 0.3), 0 2px 4px -1px oklch(0.0 0.0 0 / 0.2);
--shadow-lg: 0 10px 15px -3px oklch(0.0 0.0 0 / 0.3), 0 4px 6px -2px oklch(0.0 0.0 0 / 0.2);
}}
* {{
box-sizing: border-box;
}}
body {{
margin: 0;
background: linear-gradient(135deg, var(--bg) 0%, oklch(0.13 0.018 260) 100%);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Inter', sans-serif;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
min-height: 100vh;
}}
.container {{
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}}
header {{
text-align: center;
margin-bottom: 60px;
padding: 40px 20px;
background: var(--surface);
border-radius: var(--radius);
border: 1px solid var(--border);
box-shadow: var(--shadow);
}}
h1 {{
font-size: clamp(28px, 5vw, 42px);
margin: 0 0 16px 0;
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 50%, var(--accent-5) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 700;
letter-spacing: -0.02em;
}}
.subtitle {{
color: var(--muted);
font-size: 18px;
margin: 0 0 20px 0;
}}
.meta-info {{
display: flex;
justify-content: center;
gap: 30px;
flex-wrap: wrap;
margin-top: 20px;
}}
.meta-item {{
display: flex;
align-items: center;
gap: 8px;
color: var(--muted);
font-size: 14px;
}}
/* Section Styles */
.section {{
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 30px;
margin-bottom: 30px;
box-shadow: var(--shadow);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}}
.section:hover {{
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
}}
.section-header {{
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 24px;
}}
.section-icon {{
font-size: 24px;
}}
h2 {{
font-size: 24px;
margin: 0;
font-weight: 600;
color: var(--text);
}}
h3 {{
font-size: 18px;
margin: 20px 0 12px 0;
color: var(--accent);
font-weight: 600;
}}
/* Grid Layouts */
.grid {{
display: grid;
gap: 20px;
}}
.grid-2 {{
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}}
.grid-3 {{
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}}
.grid-4 {{
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}}
/* Flexible Layout - Prevents vertical gaps */
.flex-layout {{
display: flex;
flex-direction: column;
gap: 20px;
}}
/* Use horizontal layout only on larger screens when content is balanced */
@media (min-width: 1200px) {{
.flex-layout {{
flex-direction: row;
align-items: stretch;
}}
.flex-layout .card {{
flex: 1;
min-width: 0; /* Prevents flex items from overflowing */
}}
}}
/* Cards with APCA contrast optimization */
.card {{
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: calc(var(--radius) - 4px);
padding: 20px;
transition: all 0.2s ease;
color: var(--text);
}}
.card:hover {{
background: var(--surface-3);
border-color: var(--accent);
transform: translateY(-1px);
}}
/* Executive Summary Components - Minimal Design */
.executive-summary {{
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0;
overflow: hidden;
box-shadow: var(--shadow);
}}
.executive-header {{
background: var(--surface-2);
color: var(--text);
padding: 20px 30px;
position: relative;
border-bottom: 1px solid var(--border);
cursor: pointer;
user-select: none;
transition: background-color 0.2s ease;
}}
.executive-header:hover {{
background: var(--surface-3);
}}
.executive-header h2 {{
margin: 0;
font-size: 24px;
font-weight: 600;
display: flex;
align-items: center;
gap: 12px;
}}
.executive-header .toggle-icon {{
font-size: 18px;
transition: transform 0.3s ease;
color: var(--text-secondary);
}}
.executive-header.expanded .toggle-icon {{
transform: rotate(180deg);
}}
.executive-header p {{
margin: 6px 0 0 36px;
font-size: 14px;
color: var(--text-secondary);
opacity: 0.8;
}}
.executive-header .summary-stats {{
display: flex;
gap: 24px;
margin-top: 12px;
font-size: 13px;
color: var(--muted);
}}
.executive-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
padding: 30px;
max-height: 2000px;
overflow: hidden;
transition: max-height 0.4s ease-out, padding 0.4s ease-out;
}}
.executive-grid.collapsed {{
max-height: 0;
padding: 0 30px;
}}
.executive-metric {{
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 20px;
transition: all 0.2s ease;
position: relative;
cursor: pointer;
}}
.executive-metric:hover {{
background: var(--surface-3);
transform: translateY(-1px);
box-shadow: var(--shadow);
}}
.executive-metric.collapsible {{
border-left: 3px solid var(--accent);
}}
.executive-metric.expanded {{
background: var(--surface-3);
}}
.executive-metric.expanded::before {{
content: "▼";
position: absolute;
top: 12px;
right: 12px;
font-size: 12px;
color: var(--text-secondary);
}}
.executive-metric.collapsed::before {{
content: "▶";
position: absolute;
top: 12px;
right: 12px;
font-size: 12px;
color: var(--text-secondary);
}}
.metric-content {{
transition: max-height 0.3s ease-out;
max-height: 200px;
overflow: hidden;
}}
.metric-content.collapsed {{
max-height: 60px;
}}
.metric-header {{
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8px;
}}
.metric-label {{
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--muted);
margin-bottom: 8px;
}}
.metric-value {{
font-size: 24px;
font-weight: 700;
color: var(--text);
margin-bottom: 8px;
}}
.metric-description {{
font-size: 14px;
color: var(--text-secondary);
line-height: 1.5;
}}
.executive-insights {{
grid-column: 1 / -1;
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 24px;
}}
.insights-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-top: 16px;
}}
.insight-item {{
display: flex;
align-items: flex-start;
gap: 12px;
padding: 12px;
background: var(--surface-3);
border-radius: var(--radius-sm);
border-left: 3px solid var(--accent);
}}
.insight-icon {{
font-size: 18px;
color: var(--accent);
flex-shrink: 0;
margin-top: 2px;
}}
.insight-text {{
font-size: 13px;
color: var(--text-secondary);
line-height: 1.4;
}}
.card-title {{
font-weight: 600;
color: var(--accent);
margin-bottom: 8px;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
}}
.card-value {{
font-size: 28px;
font-weight: 700;
color: var(--text);
margin-bottom: 8px;
}}
.card-description {{
color: var(--text-secondary);
font-size: 14px;
line-height: 1.5;
}}
/* Lists */
.insight-list {{
list-style: none;
padding: 0;
margin: 0;
}}
.insight-item {{
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: calc(var(--radius) - 4px);
padding: 16px;
margin-bottom: 12px;
position: relative;
padding-left: 20px;
}}
.insight-item::before {{
content: "▸";
position: absolute;
left: 8px;
color: var(--accent);
font-weight: bold;
}}
/* Progress Bars - Minimal Design */
.progress-bar {{
background: var(--surface-3);
border: 1px solid var(--border);
border-radius: 4px;
height: 6px;
overflow: hidden;
margin: 8px 0;
}}
.progress-fill {{
height: 100%;
background: var(--accent);
border-radius: 4px;
transition: width 0.3s ease;
}}
/* Badges - Minimal Design */
.badge {{
display: inline-block;
padding: 4px 10px;
border-radius: 6px;
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.4px;
border: 1px solid var(--border);
}}
.badge-primary {{
background: var(--surface-2);
color: var(--accent);
border-color: var(--accent);
}}
.badge-success {{
background: var(--surface-2);
color: var(--accent-2);
border-color: var(--accent-2);
}}
.badge-warning {{
background: var(--surface-2);
color: var(--accent-3);
border-color: var(--accent-3);
}}
.badge-danger {{
background: var(--surface-2);
color: var(--accent-4);
border-color: var(--accent-4);
}}
.badge-info {{
background: var(--surface-2);
color: var(--accent-5);
border-color: var(--accent-5);
}}
/* Tables */
.table {{
width: 100%;
border-collapse: separate;
border-spacing: 0;
overflow: hidden;
border-radius: calc(var(--radius) - 4px);
border: 1px solid var(--border);
}}
.table th, .table td {{
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid var(--border);
}}
.table th {{
background: var(--surface-2);
font-weight: 600;
color: var(--text);
}}
.table tbody tr:hover {{
background: var(--surface-2);
}}
/* Utility Classes */
.text-center {{ text-align: center; }}
.text-muted {{ color: var(--muted); }}
.text-primary {{ color: var(--accent); }}
.text-success {{ color: var(--accent-2); }}
.text-warning {{ color: var(--accent-3); }}
.text-danger {{ color: var(--accent-4); }}
.mt-20 {{ margin-top: 20px; }}
.mb-20 {{ margin-bottom: 20px; }}
/* Footer */
footer {{
text-align: center;
padding: 40px 20px;
border-top: 1px solid var(--border);
margin-top: 60px;
color: var(--muted);
}}
/* Responsive Design */
@media (max-width: 768px) {{
.container {{
padding: 10px;
}}
.section {{
padding: 20px;
}}
.grid-2, .grid-3, .grid-4 {{
grid-template-columns: 1fr;
}}
.meta-info {{
flex-direction: column;
gap: 15px;
}}
.executive-grid {{
grid-template-columns: 1fr;
gap: 16px;
padding: 20px;
}}
.executive-header {{
padding: 20px;
}}
.executive-header h2 {{
font-size: 24px;
}}
.metric-value {{
font-size: 20px;
}}
.insights-grid {{
grid-template-columns: 1fr;
}}
}}
@media (max-width: 480px) {{
.executive-metric {{
padding: 16px;
}}
.executive-insights {{
padding: 16px;
}}
.metric-value {{
font-size: 18px;
}}
}}
</style>
</head>
<body>
<div class="container">
{content}
</div>
<footer>
<p>Generated on {timestamp} • Comprehensive Video Analysis System</p>
</footer>
</body>
</html>'''
def generate_comprehensive_report(self, video_metadata: Dict[str, Any],
chunks: List[Dict[str, Any]],
output_path: Path) -> Path:
"""Generate comprehensive HTML report with all analysis results."""
# Perform comprehensive analysis
analysis = self.analyzer.analyze_video_content(video_metadata, chunks)
# Build HTML content
html_content = self._build_html_content(video_metadata, analysis, chunks)
# Generate final HTML
final_html = self._get_html_template().format(
title=video_metadata.get('title', 'Video Analysis Report'),
content=html_content,
timestamp=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
)
# Write report
report_path = output_path / 'comprehensive_analysis_report.html'
with open(report_path, 'w', encoding='utf-8') as f:
f.write(final_html)
return report_path
def _build_html_content(self, metadata: Dict[str, Any],
analysis: Dict[str, Any],
chunks: List[Dict[str, Any]]) -> str:
"""Build the main HTML content."""
content_parts = []
# Header Section
content_parts.append(self._build_header_section(metadata, analysis))
# Executive Summary
content_parts.append(self._build_executive_summary(analysis))
# Video Purpose & Intent
content_parts.append(self._build_purpose_section(analysis))
# Content Analysis
content_parts.append(self._build_content_analysis(analysis))
# Performance Metrics
content_parts.append(self._build_performance_section(metadata, analysis))
# Quality Assessment
content_parts.append(self._build_quality_section(analysis))
# Practical Applications
content_parts.append(self._build_applications_section(analysis))
# Key Insights & Recommendations
content_parts.append(self._build_insights_section(analysis))
return '\n'.join(content_parts)
def _build_header_section(self, metadata: Dict[str, Any], analysis: Dict[str, Any]) -> str:
"""Build header section with video information."""
purpose = analysis.get('purpose_analysis', {})
target_audience = analysis.get('target_audience', {})
return f'''
<header>
<h1>{metadata.get('title', 'Video Analysis Report')}</h1>
<p class="subtitle">Comprehensive Analysis of Video Content, Performance, and Impact</p>
<div class="meta-info">
<div class="meta-item">
<span>📺</span>
<span>{metadata.get('channel', 'Unknown Channel')}</span>
</div>
<div class="meta-item">
<span>👥</span>
<span>Target: {target_audience.get('primary_audience', 'General').title()}</span>
</div>
<div class="meta-item">
<span>🎯</span>
<span>Purpose: {purpose.get('primary_purpose', 'Informational').title()}</span>
</div>
<div class="meta-item">
<span>⏱️</span>
<span>{self._format_duration(metadata.get('duration', 0))}</span>
</div>
<div class="meta-item">
<span>📅</span>
<span>{self._format_date(metadata.get('upload_date', ''))}</span>
</div>
</div>
</header>'''
def _build_executive_summary(self, analysis: Dict[str, Any]) -> str:
"""Build executive summary section with UI components."""
# Extract key metrics for the executive summary
purpose = analysis.get('purpose_analysis', {})
target_audience = analysis.get('target_audience', {})
message = analysis.get('core_message', {})
topics = analysis.get('topic_analysis', {})
performance = analysis.get('video_performance', {})
engagement = analysis.get('engagement_analysis', {})
script_quality = analysis.get('script_quality', {})
instructions = analysis.get('instructions_analysis', {})
# Extract key insights
key_insights = analysis.get('key_insights', [])[:4]
return f'''
<section class="section">
<div class="executive-summary">
<div class="executive-header" onclick="toggleExecutiveSummary()">
<h2><span class="toggle-icon">▼</span>📋 Executive Summary</h2>
<div class="summary-stats">
<span>Performance: {script_quality.get('overall_score', 0):.1f}/10</span>
<span>•</span>
<span>{engagement.get('engagement_rate', 0):.1f}% engagement</span>
<span>•</span>
<span>{len(key_insights)} insights</span>
</div>
</div>
<div class="executive-grid" id="executiveGrid">
<!-- Video Purpose & Targeting -->
<div class="executive-metric collapsible collapsed" onclick="toggleMetric(this)">
<div class="metric-content">
<div class="metric-header">
<div class="metric-label">Video Purpose</div>
<div class="metric-value">{purpose.get('primary_purpose', 'N/A').title()}</div>
</div>
<div class="metric-description">
Target: {target_audience.get('primary_audience', 'General').title()}<br>
Intent: {purpose.get('intent_level', 'N/A').title()}<br>
Clarity: {purpose.get('clarity', 'N/A').title()}
</div>
</div>
</div>
<!-- Content Performance -->
<div class="executive-metric metric-success collapsible" onclick="toggleMetric(this)">
<div class="metric-content">
<div class="metric-header">
<div class="metric-label">Content Performance</div>
<div class="metric-value">{script_quality.get('overall_score', 0):.1f}/10</div>
</div>
<div class="metric-description">
Grade: {script_quality.get('grade', 'N/A')}<br>
36 chunks analyzed<br>
{script_quality.get('word_count', 0):,} words processed
</div>
</div>
</div>
<!-- Engagement Metrics -->
<div class="executive-metric metric-info collapsible" onclick="toggleMetric(this)">
<div class="metric-content">
<div class="metric-header">
<div class="metric-label">Audience Engagement</div>
<div class="metric-value">{engagement.get('engagement_rate', 0):.1f}%</div>
</div>
<div class="metric-description">
{performance.get('views_per_day', 0):.0f} views/day<br>
{engagement.get('total_likes', 0):,} total likes<br>
{performance.get('performance_class', 'N/A')} performer
</div>
</div>
</div>
<!-- Actionable Content -->
<div class="executive-metric metric-warning collapsible" onclick="toggleMetric(this)">
<div class="metric-content">
<div class="metric-header">
<div class="metric-label">Actionable Content</div>
<div class="metric-value">{instructions.get('total_instructions', 0)}</div>
</div>
<div class="metric-description">
{instructions.get('actionable_items', 0)} action items<br>
{instructions.get('actionability_level', 'N/A')} actionability<br>
{len(instructions.get('categorized_instructions', {}))} instruction types
</div>
</div>
</div>
<!-- Core Message -->
<div class="executive-metric collapsible collapsed" onclick="toggleMetric(this)">
<div class="metric-content">
<div class="metric-header">
<div class="metric-label">Core Message</div>
<div class="metric-value" style="font-size: 16px; line-height: 1.3;">
{self._truncate_text(message.get('main_thesis', 'No explicit thesis identified.'), 80)}
</div>
</div>
<div class="metric-description">
Main topics: {', '.join(message.get('content_themes', [])[:3])}<br>
Sentiment: {message.get('sentiment', {}).get('overall_sentiment', 'Neutral')}<br>
Most discussed: {topics.get('most_discussed', ('N/A', {}))[0].upper() if topics.get('most_discussed') else 'N/A'}
</div>
</div>
</div>
<!-- Key Insights Grid -->
<div class="executive-insights">
<div class="metric-label">🎯 Key Insights</div>
<div class="insights-grid">
{self._build_executive_insights(key_insights)}
</div>
</div>
</div>
</div>
</section>
<script>
function toggleExecutiveSummary() {{
const header = document.querySelector('.executive-header');
const grid = document.querySelector('.executive-grid');
const icon = header.querySelector('.toggle-icon');
header.classList.toggle('expanded');
grid.classList.toggle('collapsed');
}}
function toggleMetric(element) {{
element.classList.toggle('collapsed');
element.classList.toggle('expanded');
const content = element.querySelector('.metric-content');
content.classList.toggle('collapsed');
}}
</script>'''
def _build_purpose_section(self, analysis: Dict[str, Any]) -> str:
"""Build purpose and intent analysis section."""
purpose = analysis.get('purpose_analysis', {})
target_audience = analysis.get('target_audience', {})
video_style = analysis.get('video_style', {})
return f'''
<section class="section">
<div class="section-header">
<span class="section-icon">🎯</span>
<h2>Video Purpose & Target Audience</h2>
</div>
<div class="grid grid-3">
<div class="card">
<div class="card-title">Primary Purpose</div>
<div class="card-value">{purpose.get('primary_purpose', 'N/A').title()}</div>
<div class="card-description">Intent Level: {purpose.get('intent_level', 'N/A').title()}</div>
<div class="card-description">Clarity: {purpose.get('clarity', 'N/A').title()}</div>
</div>
<div class="card">
<div class="card-title">Target Audience</div>
<div class="card-value">{target_audience.get('primary_audience', 'N/A').title()}</div>
<div class="card-description">Expertise: {target_audience.get('expertise_level', 'N/A').title()}</div>
<div class="card-description">Format: {target_audience.get('content_format', 'N/A').title()}</div>
</div>
<div class="card">
<div class="card-title">Video Style</div>
<div class="card-value">{video_style.get('dominant_style', 'N/A').title()}</div>
<div class="card-description">Format: {video_style.get('format_type', 'N/A').title()}</div>
<div class="card-description">Tone: {video_style.get('tone', 'N/A').title()}</div>
</div>
</div>
</section>'''
def _build_content_analysis(self, analysis: Dict[str, Any]) -> str:
"""Build content analysis section."""
message = analysis.get('core_message', {})
topics = analysis.get('topic_analysis', {})
things_mentioned = analysis.get('things_mentioned', {})
speakers = analysis.get('speaker_analysis', {})
return f'''
<section class="section">
<div class="section-header">
<span class="section-icon">🔍</span>
<h2>Content Analysis</h2>
</div>
<div class="grid grid-2">
<div class="card">
<h3>Core Message</h3>
<p class="text-muted">{message.get('main_thesis', 'No explicit thesis found.')}</p>
<div class="mt-20">
<div class="card-title">Content Themes</div>
<div class="grid grid-4">
{self._build_theme_badges(message.get('content_themes', []))}
</div>
</div>
<div class="mt-20">
<div class="card-title">Sentiment Analysis</div>
<p>Sentiment: <span class="badge badge-{self._get_sentiment_class(message.get('sentiment', {}).get('overall_sentiment', 'Neutral'))}">{message.get('sentiment', {}).get('overall_sentiment', 'Neutral')}</span></p>
</div>
</div>
<div class="card">
<h3>Speaker Analysis</h3>
<p><strong>Speaking Perspective:</strong> {speakers.get('speaking_perspective', 'Unknown')}</p>
<p><strong>Estimated Speakers:</strong> {speakers.get('estimated_speakers', 'Unknown')}</p>
<p><strong>Speaking Style:</strong> {speakers.get('speaking_style', 'Unknown').title()}</p>
<div class="mt-20">
<div class="card-title">Speaker Characteristics</div>
<ul class="insight-list">
{self._build_speaker_characteristics(speakers.get('speaker_characteristics', {}))}
</ul>
</div>
</div>
</div>
<div class="flex-layout mt-20">
<div class="card">
<h3>Topic Analysis</h3>
<p><strong>Most Discussed:</strong> {topics.get('most_discussed', ('N/A', {}))[0].upper() if topics.get('most_discussed') else 'N/A'}</p>
<p><strong>Total Categories:</strong> {len(topics.get('categorized_topics', {}))}</p>
<div class="mt-20">
<div class="card-title">Topic Categories</div>
<div class="grid grid-3">
{self._build_topic_cards(topics.get('categorized_topics', {}))}
</div>
</div>
</div>
<div class="card">
<h3>Entities Mentioned</h3>
<p><strong>Total Entities:</strong> {things_mentioned.get('total_entities_mentioned', 0)}</p>
<div class="mt-20">
<div class="card-title">Key Entities</div>
<div class="grid grid-2">
{self._build_entity_cards(things_mentioned)}
</div>
</div>
</div>
</div>
</section>'''
def _build_performance_section(self, metadata: Dict[str, Any], analysis: Dict[str, Any]) -> str:
"""Build performance metrics section."""
performance = analysis.get('video_performance', {})
engagement = analysis.get('engagement_analysis', {})
views = metadata.get('view_count', 0)
likes = metadata.get('like_count', 0)
return f'''
<section class="section">
<div class="section-header">
<span class="section-icon">📈</span>
<h2>Performance Metrics</h2>
</div>
<div class="grid grid-4">
<div class="card text-center">
<div class="card-title">Total Views</div>
<div class="card-value">{views:,}</div>
<div class="card-description">Lifetime performance</div>
</div>
<div class="card text-center">
<div class="card-title">Engagement Rate</div>
<div class="card-value">{engagement.get('engagement_rate', 0):.1f}%</div>
<div class="card-description">Likes per view</div>
</div>
<div class="card text-center">
<div class="card-title">Views Per Day</div>
<div class="card-value">{performance.get('views_per_day', 0):.0f}</div>
<div class="card-description">Daily average</div>
</div>
<div class="card text-center">
<div class="card-title">Performance Class</div>
<div class="card-value" style="font-size: 20px;">{performance.get('performance_class', 'N/A')}</div>
<div class="card-description">Content classification</div>
</div>
</div>
<div class="flex-layout mt-20">
<div class="card">
<h3>Engagement Analysis</h3>
<div class="grid grid-2">
<div>
<p><strong>Total Likes:</strong> {likes:,}</p>
<p><strong>Predicted Score:</strong> {engagement.get('predicted_engagement_score', 0):.1f}/10</p>
</div>
<div>
<div class="card-title">Engagement Indicators</div>
{self._build_engagement_indicators(engagement.get('engagement_indicators', {}))}
</div>
</div>
</div>
<div class="card">
<h3>Viral Potential & Impact</h3>
<p><strong>Viral Potential:</strong> <span class="badge badge-{self._get_viral_class(performance.get('viral_potential', 'Low'))}">{performance.get('viral_potential', 'Low')}</span></p>
<p><strong>Real World Impact:</strong> <span class="badge badge-{self._get_impact_class(performance.get('real_world_impact', {}).get('impact_score', 0))}">{performance.get('real_world_impact', {}).get('impact_level', 'Low')}</span></p>
<div class="mt-20">
<div class="card-title">Impact Indicators</div>
{self._build_impact_indicators(performance.get('real_world_impact', {}))}
</div>
</div>
</div>
</section>'''
def _build_quality_section(self, analysis: Dict[str, Any]) -> str:
"""Build quality assessment section."""
script_quality = analysis.get('script_quality', {})
return f'''
<section class="section">
<div class="section-header">
<span class="section-icon">✅</span>
<h2>Content Quality Assessment</h2>
</div>
<div class="flex-layout">
<div class="card">
<h3>Script Quality Score</h3>
<div class="card-value">{script_quality.get('overall_score', 0):.1f}/10</div>
<div class="card-description">Grade: <span class="badge badge-{self._get_grade_class(script_quality.get('grade', 'C'))}">{script_quality.get('grade', 'C')}</span></div>
<div class="progress-bar mt-20">
<div class="progress-fill" style="width: {script_quality.get('overall_score', 0) * 10}%"></div>
</div>
<div class="mt-20">
<div class="card-title">Quality Components</div>
<div class="grid grid-2">
{self._build_quality_components(script_quality.get('quality_components', {}))}
</div>
</div>
</div>
<div class="card">
<h3>Script Strengths</h3>
<ul class="insight-list">
{self._build_strengths_list(script_quality.get('strengths', []))}
</ul>
<h3 class="mt-20">Improvement Areas</h3>
<ul class="insight-list">
{self._build_improvements_list(script_quality.get('improvement_areas', []))}
</ul>
</div>
</div>
</section>'''
def _build_applications_section(self, analysis: Dict[str, Any]) -> str:
"""Build practical applications section."""
instructions = analysis.get('instructions_analysis', {})
practical_value = analysis.get('practical_value', {})
real_world = analysis.get('real_world_applications', {})
return f'''
<section class="section">
<div class="section-header">
<span class="section-icon">💡</span>
<h2>Practical Applications & Instructions</h2>
</div>
<div class="grid grid-3">
<div class="card">
<div class="card-title">Actionable Content</div>
<div class="card-value">{instructions.get('total_instructions', 0)}</div>
<div class="card-description">Total instructions found</div>
<div class="mt-20">
<div class="card-title">Actionability</div>
<p><span class="badge badge-success">{instructions.get('actionability_level', 'Low')}</span></p>
<p>Actionable items: {instructions.get('actionable_items', 0)}</p>
</div>
</div>
<div class="card">
<div class="card-title">Practical Value</div>
<div class="card-value">{practical_value.get('practical_value_assessment', 'Medium')}</div>
<div class="card-description">Overall practicality</div>
<div class="progress-bar mt-20">
<div class="progress-fill" style="width: {practical_value.get('implementation_ease', 0) * 10}%"></div>
</div>
<p class="text-muted mt-10">Implementation Ease: {practical_value.get('implementation_ease', 0):.1f}/10</p>
</div>
<div class="card">
<div class="card-title">Applications</div>
<div class="card-value">{real_world.get('breadth_of_applications', 0)}</div>
<div class="card-description">Domain areas covered</div>
<div class="mt-20">
<div class="card-title">Primary Domain</div>
<p><span class="badge badge-primary">{real_world.get('primary_application_domain', 'General').title()}</span></p>
</div>
</div>
</div>
<div class="flex-layout mt-20">
<div class="card">
<h3>Instruction Categories</h3>
<div class="grid grid-3">
{self._build_instruction_categories(instructions.get('categorized_instructions', {}))}
</div>
</div>
<div class="card">
<h3>Key Action Items</h3>
<ul class="insight-list">
{self._build_action_items_list(analysis.get('action_items', []))}
</ul>
</div>
</div>
</section>'''
def _build_insights_section(self, analysis: Dict[str, Any]) -> str:
"""Build key insights and recommendations section."""
key_insights = analysis.get('key_insights', [])
recommendations = analysis.get('recommendations', [])
persuasive = analysis.get('persuasive_elements', {})
return f'''
<section class="section">
<div class="section-header">
<span class="section-icon">🎯</span>
<h2>Key Insights & Recommendations</h2>
</div>
<div class="flex-layout">
<div class="card">
<h3>Top Insights</h3>
<ul class="insight-list">
{self._build_insights_list(key_insights)}
</ul>
</div>
<div class="card">
<h3>Recommendations</h3>
<ul class="insight-list">
{self._build_recommendations_list(recommendations)}
</ul>
</div>
</div>
<div class="card mt-20">
<h3>Persuasive Analysis</h3>
<div class="grid grid-2">
<div>
<p><strong>Dominant Technique:</strong> {persuasive.get('dominant_technique', 'None').title()}</p>
<p><strong>Persuasion Intensity:</strong> {persuasive.get('persuasion_intensity', 0):.1f}/10</p>
</div>
<div>
<div class="card-title">Techniques Used</div>
{self._build_persuasion_techniques(persuasive.get('persuasion_techniques', {}))}
</div>
</div>
</div>
</section>'''
# Helper methods for building HTML components
def _format_duration(self, seconds: int) -> str:
if not seconds:
return "N/A"
hours = seconds // 3600
minutes = (seconds % 3600) // 60
secs = seconds % 60
if hours > 0:
return f"{hours}h {minutes}m {secs}s"
elif minutes > 0:
return f"{minutes}m {secs}s"
else:
return f"{secs}s"
def _format_date(self, date_str: str) -> str:
if not date_str or len(date_str) != 8:
return "N/A"
try:
year = date_str[:4]
month = date_str[4:6]
day = date_str[6:8]
return f"{day}/{month}/{year}"
except:
return date_str
def _get_sentiment_class(self, sentiment: str) -> str:
sentiment_map = {
'Positive': 'success',
'Negative': 'danger',
'Neutral': 'info'
}
return sentiment_map.get(sentiment, 'info')
def _get_viral_class(self, viral: str) -> str:
viral_map = {
'High': 'success',
'Medium': 'warning',
'Low': 'danger'
}
return viral_map.get(viral, 'info')
def _get_impact_class(self, impact_score: float) -> str:
if impact_score >= 7:
return 'success'
elif impact_score >= 4:
return 'warning'
else:
return 'danger'
def _get_grade_class(self, grade: str) -> str:
grade_map = {
'A': 'success',
'B': 'primary',
'C': 'warning',
'D': 'danger',
'F': 'danger'
}
return grade_map.get(grade, 'info')
def _build_theme_badges(self, themes: List[str]) -> str:
if not themes:
return '<div class="card-description">No themes identified</div>'
return ''.join([f'<span class="badge badge-primary" style="margin: 2px;">{theme.title()}</span>' for theme in themes[:8]])
def _build_speaker_characteristics(self, characteristics: Dict[str, Any]) -> str:
if not characteristics:
return '<li class="insight-item">No speaker characteristics identified</li>'
items = []
for characteristic, detected in characteristics.items():
if detected:
items.append(f'<li class="insight-item">{characteristic.replace("_", " ").title()}</li>')
return ''.join(items) if items else '<li class="insight-item">No notable characteristics</li>'
def _build_topic_cards(self, topics: Dict[str, List[str]]) -> str:
if not topics:
return '<div class="card-description">No topics categorized</div>'
cards = []
for category, topic_list in list(topics.items())[:6]:
cards.append(f'''
<div class="card">
<div class="card-title">{category.title()}</div>
<div class="card-description">{len(topic_list)} topics</div>
</div>''')
return ''.join(cards)
def _build_entity_cards(self, things_mentioned: Dict[str, Any]) -> str:
cards = []
entity_types = ['companies', 'people', 'technologies', 'concepts']
for entity_type in entity_types:
entities = things_mentioned.get(entity_type, [])[:3]
if entities:
cards.append(f'''
<div class="card">
<div class="card-title">{entity_type.title()}</div>
<div class="card-description">{', '.join(entities[:3])}</div>
</div>''')
return ''.join(cards) if cards else '<div class="card-description">No entities identified</div>'
def _build_engagement_indicators(self, indicators: Dict[str, int]) -> str:
if not indicators:
return '<div class="card-description">No engagement indicators</div>'
items = []
for indicator, count in indicators.items():
if count > 0:
items.append(f'<div class="card-description">{indicator.replace("_", " ").title()}: {count}</div>')
return ''.join(items) if items else '<div class="card-description">No indicators found</div>'
def _build_impact_indicators(self, impact: Dict[str, Any]) -> str:
if not impact or not impact.get('impact_indicators'):
return '<div class="card-description">No impact indicators</div>'
indicators = impact.get('impact_indicators', {})
items = []
for indicator, score in indicators.items():
items.append(f'''
<div style="margin-bottom: 8px;">
<div style="display: flex; justify-content: space-between;">
<span>{indicator.replace("_", " ").title()}</span>
<span>{score}</span>
</div>
<div class="progress-bar" style="height: 4px;">
<div class="progress-fill" style="width: {min(100, score * 10)}%"></div>
</div>
</div>''')
return ''.join(items)
def _build_quality_components(self, components: Dict[str, float]) -> str:
if not components:
return '<div class="card-description">No components analyzed</div>'
items = []
for component, score in components.items():
items.append(f'''
<div class="card" style="padding: 12px;">
<div class="card-title">{component.replace("_", " ").title()}</div>
<div class="card-value" style="font-size: 20px;">{score:.1f}/10</div>
</div>''')
return ''.join(items)
def _build_strengths_list(self, strengths: List[str]) -> str:
if not strengths:
return '<li class="insight-item">No strengths identified</li>'
return ''.join([f'<li class="insight-item">{strength}</li>' for strength in strengths[:5]])
def _build_improvements_list(self, improvements: List[str]) -> str:
if not improvements:
return '<li class="insight-item">No improvement areas identified</li>'
return ''.join([f'<li class="insight-item">{improvement}</li>' for improvement in improvements[:5]])
def _build_instruction_categories(self, categories: Dict[str, List[str]]) -> str:
if not categories:
return '<div class="card-description">No categories found</div>'
items = []
for category, items_list in categories.items():
if items_list:
items.append(f'''
<div class="card">
<div class="card-title">{category.title()}</div>
<div class="card-description">{len(items_list)} items</div>
</div>''')
return ''.join(items) if items else '<div class="card-description">No categories</div>'
def _build_action_items_list(self, action_items: List[Dict[str, Any]]) -> str:
if not action_items:
return '<li class="insight-item">No action items identified</li>'
items_html = []
for item in action_items[:8]:
if isinstance(item, dict):
# New format with text, priority, category, timeframe
text = item.get('text', '')
priority = item.get('priority', '')
category = item.get('category', '')
timeframe = item.get('timeframe', '')
priority_badge = f' <span class="badge badge-{self._get_priority_color(priority)}">{priority}</span>' if priority else ''
category_badge = f' <span class="badge badge-info">{category}</span>' if category else ''
timeframe_badge = f' <span class="badge badge-primary">{timeframe}</span>' if timeframe else ''
items_html.append(f'<li class="insight-item">{text}{priority_badge}{category_badge}{timeframe_badge}</li>')
else:
# Legacy format (string)
items_html.append(f'<li class="insight-item">{item}</li>')
return ''.join(items_html)
def _get_priority_color(self, priority: str) -> str:
"""Get badge color for priority level."""
priority_lower = priority.lower()
if priority_lower in ['high', 'urgent']:
return 'danger'
elif priority_lower in ['medium', 'normal']:
return 'warning'
elif priority_lower in ['low', 'minor']:
return 'success'
else:
return 'info'
def _build_insights_list(self, insights: List[str]) -> str:
if not insights:
return '<li class="insight-item">No insights available</li>'
return ''.join([f'<li class="insight-item">{insight}</li>' for insight in insights[:6]])
def _build_recommendations_list(self, recommendations: List[str]) -> str:
if not recommendations:
return '<li class="insight-item">No recommendations available</li>'
return ''.join([f'<li class="insight-item">{rec}</li>' for rec in recommendations[:6]])
def _build_persuasion_techniques(self, techniques: Dict[str, int]) -> str:
if not techniques:
return '<div class="card-description">No techniques identified</div>'
items = []
for technique, count in techniques.items():
if count > 0:
items.append(f'<div class="card-description">{technique.replace("_", " ").title()}: {count}</div>')
return ''.join(items) if items else '<div class="card-description">No techniques used</div>'
def _truncate_text(self, text: str, max_length: int) -> str:
"""Truncate text to specified length with ellipsis."""
if len(text) <= max_length:
return text
return text[:max_length].rstrip() + "..."
def _build_executive_insights(self, insights: List[str]) -> str:
"""Build executive insights as UI components."""
if not insights:
return '''
<div class="insight-item">
<span class="insight-icon">💡</span>
<span class="insight-text">No specific insights available</span>
</div>'''
insight_icons = ['🎯', '💡', '📈', '🔍', '⚡', '🌟']
items = []
for i, insight in enumerate(insights[:6]):
icon = insight_icons[i % len(insight_icons)]
items.append(f'''
<div class="insight-item">
<span class="insight-icon">{icon}</span>
<span class="insight-text">{self._truncate_text(insight, 120)}</span>
</div>''')
return ''.join(items)
def main():
"""Generate comprehensive report for the test video."""
generator = ComprehensiveReportGenerator()
# Load existing data
video_dir = Path('KNOWLEDGE_YOUTUBE/yoycgOMq1tI')
metadata_file = video_dir / 'exports' / 'metadata.json'
chunks_file = video_dir / 'exports' / 'chunks.jsonl'
# Load data
with open(metadata_file, 'r') as f:
metadata = json.load(f)
chunks = []
with open(chunks_file, 'r') as f:
for line in f:
chunks.append(json.loads(line))
# Generate report
report_path = generator.generate_comprehensive_report(metadata, chunks, video_dir / 'exports')
print(f"✅ Comprehensive report generated: {report_path}")
# Open in browser
import subprocess
subprocess.run(['open', str(report_path)])
if __name__ == "__main__":
main()