staffily / cbh /api /reports /utils.py
brestok's picture
Add report generation and update functionality
8980c38
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"