PlanOpticon

Source Blame History 199 lines
cd8f2f9… leo 1 """Multi-format output orchestration."""
cd8f2f9… leo 2
cd8f2f9… leo 3 import logging
cd8f2f9… leo 4 from pathlib import Path
cd8f2f9… leo 5 from typing import Optional
cd8f2f9… leo 6
287a3bb… leo 7 from tqdm import tqdm
287a3bb… leo 8
829e24a… leo 9 from video_processor.models import VideoManifest
cd8f2f9… leo 10 from video_processor.utils.rendering import render_mermaid, reproduce_chart
cd8f2f9… leo 11
cd8f2f9… leo 12 logger = logging.getLogger(__name__)
cd8f2f9… leo 13
cd8f2f9… leo 14
cd8f2f9… leo 15 def generate_html_report(
cd8f2f9… leo 16 manifest: VideoManifest,
cd8f2f9… leo 17 output_dir: Path,
cd8f2f9… leo 18 ) -> Optional[Path]:
cd8f2f9… leo 19 """
cd8f2f9… leo 20 Generate a self-contained HTML report with embedded diagrams.
cd8f2f9… leo 21
cd8f2f9… leo 22 Reads the markdown analysis and enriches it with rendered SVGs
cd8f2f9… leo 23 and mermaid.js for any unrendered blocks.
cd8f2f9… leo 24 """
cd8f2f9… leo 25 output_dir = Path(output_dir)
cd8f2f9… leo 26 results_dir = output_dir / "results"
cd8f2f9… leo 27 results_dir.mkdir(parents=True, exist_ok=True)
cd8f2f9… leo 28
cd8f2f9… leo 29 # Read markdown if available
cd8f2f9… leo 30 md_content = ""
cd8f2f9… leo 31 if manifest.analysis_md:
cd8f2f9… leo 32 md_path = output_dir / manifest.analysis_md
cd8f2f9… leo 33 if md_path.exists():
cd8f2f9… leo 34 md_content = md_path.read_text()
cd8f2f9… leo 35
cd8f2f9… leo 36 # Convert markdown to HTML
cd8f2f9… leo 37 try:
cd8f2f9… leo 38 import markdown
cd8f2f9… leo 39
cd8f2f9… leo 40 html_body = markdown.markdown(
cd8f2f9… leo 41 md_content,
cd8f2f9… leo 42 extensions=["fenced_code", "tables", "toc"],
cd8f2f9… leo 43 )
cd8f2f9… leo 44 except ImportError:
cd8f2f9… leo 45 logger.warning("markdown library not available, using raw text")
cd8f2f9… leo 46 html_body = f"<pre>{md_content}</pre>"
cd8f2f9… leo 47
cd8f2f9… leo 48 # Build sections for key points, action items
cd8f2f9… leo 49 sections = []
cd8f2f9… leo 50
cd8f2f9… leo 51 if manifest.key_points:
cd8f2f9… leo 52 kp_html = "<h2>Key Points</h2><ul>"
cd8f2f9… leo 53 for kp in manifest.key_points:
cd8f2f9… leo 54 kp_html += f"<li><strong>{kp.point}</strong>"
cd8f2f9… leo 55 if kp.details:
cd8f2f9… leo 56 kp_html += f" - {kp.details}"
cd8f2f9… leo 57 kp_html += "</li>"
cd8f2f9… leo 58 kp_html += "</ul>"
cd8f2f9… leo 59 sections.append(kp_html)
cd8f2f9… leo 60
cd8f2f9… leo 61 if manifest.action_items:
cd8f2f9… leo 62 ai_html = "<h2>Action Items</h2><ul>"
cd8f2f9… leo 63 for ai in manifest.action_items:
cd8f2f9… leo 64 ai_html += f"<li><strong>{ai.action}</strong>"
cd8f2f9… leo 65 if ai.assignee:
cd8f2f9… leo 66 ai_html += f" (assigned to: {ai.assignee})"
cd8f2f9… leo 67 if ai.deadline:
cd8f2f9… leo 68 ai_html += f" — due: {ai.deadline}"
cd8f2f9… leo 69 ai_html += "</li>"
cd8f2f9… leo 70 ai_html += "</ul>"
cd8f2f9… leo 71 sections.append(ai_html)
cd8f2f9… leo 72
cd8f2f9… leo 73 # Embed diagram SVGs
cd8f2f9… leo 74 if manifest.diagrams:
cd8f2f9… leo 75 diag_html = "<h2>Diagrams</h2>"
cd8f2f9… leo 76 for i, d in enumerate(manifest.diagrams):
cd8f2f9… leo 77 diag_html += f"<h3>Diagram {i + 1}: {d.description or d.diagram_type.value}</h3>"
cd8f2f9… leo 78 svg_path = output_dir / d.svg_path if d.svg_path else None
cd8f2f9… leo 79 if svg_path and svg_path.exists():
cd8f2f9… leo 80 svg_content = svg_path.read_text()
cd8f2f9… leo 81 diag_html += f'<div class="diagram">{svg_content}</div>'
cd8f2f9… leo 82 elif d.image_path:
829e24a… leo 83 diag_html += (
829e24a… leo 84 f'<img src="{d.image_path}" alt="Diagram {i + 1}" style="max-width:100%">'
829e24a… leo 85 )
cd8f2f9… leo 86 if d.mermaid:
cd8f2f9… leo 87 diag_html += f'<pre class="mermaid">{d.mermaid}</pre>'
cd8f2f9… leo 88 sections.append(diag_html)
cd8f2f9… leo 89
cd8f2f9… leo 90 title = manifest.video.title or "PlanOpticon Analysis"
cd8f2f9… leo 91 full_html = f"""<!DOCTYPE html>
cd8f2f9… leo 92 <html lang="en">
cd8f2f9… leo 93 <head>
cd8f2f9… leo 94 <meta charset="utf-8">
cd8f2f9… leo 95 <title>{title}</title>
cd8f2f9… leo 96 <style>
cd8f2f9… leo 97 body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
cd8f2f9… leo 98 max-width: 960px; margin: 0 auto; padding: 20px; line-height: 1.6; color: #333; }}
cd8f2f9… leo 99 h1 {{ color: #1a1a2e; border-bottom: 2px solid #e0e0e0; padding-bottom: 10px; }}
cd8f2f9… leo 100 h2 {{ color: #16213e; margin-top: 2em; }}
cd8f2f9… leo 101 h3 {{ color: #0f3460; }}
cd8f2f9… leo 102 pre {{ background: #f5f5f5; padding: 12px; border-radius: 6px; overflow-x: auto; }}
cd8f2f9… leo 103 code {{ background: #f5f5f5; padding: 2px 6px; border-radius: 3px; }}
cd8f2f9… leo 104 table {{ border-collapse: collapse; width: 100%; margin: 1em 0; }}
cd8f2f9… leo 105 th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
cd8f2f9… leo 106 th {{ background: #f0f0f0; }}
cd8f2f9… leo 107 .diagram {{ margin: 1em 0; text-align: center; }}
cd8f2f9… leo 108 .diagram svg {{ max-width: 100%; height: auto; }}
cd8f2f9… leo 109 img {{ max-width: 100%; height: auto; }}
cd8f2f9… leo 110 ul {{ padding-left: 1.5em; }}
cd8f2f9… leo 111 li {{ margin: 0.3em 0; }}
cd8f2f9… leo 112 </style>
cd8f2f9… leo 113 <script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
cd8f2f9… leo 114 <script>mermaid.initialize({{startOnLoad: true}});</script>
cd8f2f9… leo 115 </head>
cd8f2f9… leo 116 <body>
cd8f2f9… leo 117 <h1>{title}</h1>
cd8f2f9… leo 118 {html_body}
cd8f2f9… leo 119 {"".join(sections)}
cd8f2f9… leo 120 </body>
cd8f2f9… leo 121 </html>"""
cd8f2f9… leo 122
cd8f2f9… leo 123 html_path = results_dir / "analysis.html"
cd8f2f9… leo 124 html_path.write_text(full_html)
cd8f2f9… leo 125 logger.info(f"Generated HTML report: {html_path}")
cd8f2f9… leo 126 return html_path
cd8f2f9… leo 127
cd8f2f9… leo 128
cd8f2f9… leo 129 def generate_pdf_report(html_path: Path, output_path: Path) -> Optional[Path]:
cd8f2f9… leo 130 """
cd8f2f9… leo 131 Convert HTML report to PDF using weasyprint.
cd8f2f9… leo 132
cd8f2f9… leo 133 Returns the PDF path or None if weasyprint is not available.
cd8f2f9… leo 134 """
cd8f2f9… leo 135 try:
cd8f2f9… leo 136 from weasyprint import HTML
cd8f2f9… leo 137
cd8f2f9… leo 138 HTML(filename=str(html_path)).write_pdf(str(output_path))
cd8f2f9… leo 139 logger.info(f"Generated PDF report: {output_path}")
cd8f2f9… leo 140 return output_path
cd8f2f9… leo 141 except ImportError:
cd8f2f9… leo 142 logger.info("weasyprint not installed, skipping PDF generation")
cd8f2f9… leo 143 return None
cd8f2f9… leo 144 except Exception as e:
cd8f2f9… leo 145 logger.warning(f"PDF generation failed: {e}")
cd8f2f9… leo 146 return None
cd8f2f9… leo 147
cd8f2f9… leo 148
cd8f2f9… leo 149 def export_all_formats(
cd8f2f9… leo 150 output_dir: str | Path,
cd8f2f9… leo 151 manifest: VideoManifest,
cd8f2f9… leo 152 ) -> VideoManifest:
cd8f2f9… leo 153 """
cd8f2f9… leo 154 Render all diagrams and generate HTML/PDF reports.
cd8f2f9… leo 155
cd8f2f9… leo 156 Updates manifest with output file paths and returns it.
cd8f2f9… leo 157 """
cd8f2f9… leo 158 output_dir = Path(output_dir)
cd8f2f9… leo 159
cd8f2f9… leo 160 # Render mermaid diagrams to SVG/PNG
829e24a… leo 161 for i, diagram in enumerate(
829e24a… leo 162 tqdm(manifest.diagrams, desc="Rendering diagrams", unit="diag") if manifest.diagrams else []
829e24a… leo 163 ):
cd8f2f9… leo 164 if diagram.mermaid:
cd8f2f9… leo 165 diagrams_dir = output_dir / "diagrams"
cd8f2f9… leo 166 prefix = f"diagram_{i}"
cd8f2f9… leo 167 paths = render_mermaid(diagram.mermaid, diagrams_dir, prefix)
cd8f2f9… leo 168 if "svg" in paths:
cd8f2f9… leo 169 diagram.svg_path = f"diagrams/{prefix}.svg"
cd8f2f9… leo 170 if "png" in paths:
cd8f2f9… leo 171 diagram.png_path = f"diagrams/{prefix}.png"
cd8f2f9… leo 172 if "mermaid" in paths and not diagram.mermaid_path:
cd8f2f9… leo 173 diagram.mermaid_path = f"diagrams/{prefix}.mermaid"
cd8f2f9… leo 174
cd8f2f9… leo 175 # Reproduce charts
cd8f2f9… leo 176 if diagram.chart_data and diagram.diagram_type.value == "chart":
cd8f2f9… leo 177 chart_paths = reproduce_chart(
cd8f2f9… leo 178 diagram.chart_data,
cd8f2f9… leo 179 output_dir / "diagrams",
cd8f2f9… leo 180 f"diagram_{i}",
cd8f2f9… leo 181 )
cd8f2f9… leo 182 if "svg" in chart_paths:
cd8f2f9… leo 183 diagram.svg_path = f"diagrams/diagram_{i}_chart.svg"
cd8f2f9… leo 184 if "png" in chart_paths:
cd8f2f9… leo 185 diagram.png_path = f"diagrams/diagram_{i}_chart.png"
cd8f2f9… leo 186
cd8f2f9… leo 187 # Generate HTML report
cd8f2f9… leo 188 html_path = generate_html_report(manifest, output_dir)
cd8f2f9… leo 189 if html_path:
cd8f2f9… leo 190 manifest.analysis_html = str(html_path.relative_to(output_dir))
cd8f2f9… leo 191
cd8f2f9… leo 192 # Generate PDF from HTML
cd8f2f9… leo 193 if html_path:
cd8f2f9… leo 194 pdf_path = output_dir / "results" / "analysis.pdf"
cd8f2f9… leo 195 result = generate_pdf_report(html_path, pdf_path)
cd8f2f9… leo 196 if result:
cd8f2f9… leo 197 manifest.analysis_pdf = str(pdf_path.relative_to(output_dir))
cd8f2f9… leo 198
cd8f2f9… leo 199 return manifest

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button