| from datetime import datetime |
| import re |
| import markdown |
| from jinja2 import Environment, FileSystemLoader, select_autoescape |
| from weasyprint import HTML |
|
|
| from cbh.api.reports.dto import StackType, ToolScore, ToolType |
| from cbh.api.reports.models import ReportModel |
| from cbh.core.config import settings |
|
|
|
|
| _STACK_TYPE_LABELS = { |
| StackType.RECOMMENDED: "Recommended", |
| StackType.FASTEST: "Fastest", |
| StackType.CHEAPEST: "Cheapest", |
| } |
|
|
| _TOOL_TYPE_LABELS = { |
| ToolType.RECOMMENDED: "Recommended", |
| ToolType.ALTERNATIVE: "Alternative", |
| } |
|
|
| _TOOL_SCORE_LABELS = { |
| ToolScore.GREAT: "Great", |
| ToolScore.GOOD: "Good", |
| ToolScore.AVERAGE: "Average", |
| ToolScore.POOR: "Poor", |
| } |
|
|
| _TOOL_SCORE_CLASSES = { |
| ToolScore.GREAT: "great", |
| ToolScore.GOOD: "good", |
| ToolScore.AVERAGE: "average", |
| ToolScore.POOR: "poor", |
| } |
|
|
| _MARKDOWN_EXTENSIONS = ["extra", "sane_lists", "nl2br"] |
|
|
|
|
| def _render_markdown(value: str | None) -> str: |
| if not value: |
| return "" |
| return markdown.markdown( |
| value, extensions=_MARKDOWN_EXTENSIONS, output_format="html5" |
| ) |
|
|
|
|
| def _get_template_env() -> Environment: |
| templates_path = settings.BASE_DIR / "cbh" / "templates" / "report" |
| env = Environment( |
| loader=FileSystemLoader(str(templates_path)), |
| autoescape=select_autoescape(["html", "xml"]), |
| ) |
| env.globals["stack_type_label"] = lambda t: _STACK_TYPE_LABELS.get(t, str(t)) |
| env.globals["tool_type_label"] = lambda t: _TOOL_TYPE_LABELS.get(t, str(t)) |
| env.globals["tool_score_label"] = lambda s: _TOOL_SCORE_LABELS.get(s, str(s)) |
| env.globals["tool_score_class"] = lambda s: _TOOL_SCORE_CLASSES.get(s, "good") |
| return env |
|
|
|
|
| def _resolve_recipient_name(report: ReportModel) -> str: |
| account = report.account |
| return account.name or account.email or "Founder" |
|
|
|
|
| def _normalize_stack(report: ReportModel) -> list: |
| normalized = [] |
| for group in report.stack or []: |
| if isinstance(group, dict): |
| continue |
| if not getattr(group, "tools", None): |
| continue |
| normalized.append(group) |
| return normalized |
|
|
|
|
| def download_report_obj(report: ReportModel) -> bytes: |
| """ |
| Render a report as a styled PDF document and return raw PDF bytes. |
| """ |
| env = _get_template_env() |
| template = env.get_template("base.html") |
|
|
| html_content = template.render( |
| report=report, |
| recipient_name=_resolve_recipient_name(report), |
| generated_at=datetime.utcnow().strftime("%B %d, %Y"), |
| stack=_normalize_stack(report), |
| review_html=_render_markdown(report.review), |
| deep_analysis_html=_render_markdown(report.deepAnalysis), |
| monetization_html=_render_markdown(report.monetization), |
| risks_html=_render_markdown(report.risks), |
| marketing_plan_html=_render_markdown(report.marketingPlan), |
| next_steps_html=_render_markdown(report.nextSteps), |
| ) |
|
|
| return HTML(string=html_content).write_pdf() |
|
|
|
|
| def prepare_filename(name: str) -> str: |
| safe_name = re.sub(r"[^A-Za-z0-9._-]+", "_", name).strip("_") or "report" |
| return f"{safe_name}.pdf" |
|
|