FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring, source collection, LLM-based change analysis, enrichment, and account security (Turnstile, escalating lockout, email verification).
118 lines
4.0 KiB
Python
118 lines
4.0 KiB
Python
"""Renders a ReportContent (structured, from the LLM) into the Markdown
|
|
document the API/UI serve alongside the JSON - see spec section 6G for the
|
|
16-section layout this follows.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from app.prompts.report_generation import Finding, InferredProject, ReportContent
|
|
|
|
|
|
def _render_findings(findings: list[Finding]) -> str:
|
|
if not findings:
|
|
return "_No findings for this section from the current evidence._\n"
|
|
lines = []
|
|
for f in findings:
|
|
lines.append(f"- **{f.headline}** _(confidence: {f.confidence.value.replace('_', ' ')})_")
|
|
lines.append(f" {f.summary}")
|
|
if f.date:
|
|
lines.append(f" _Date: {f.date}_")
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
def _render_projects(projects: list[InferredProject]) -> str:
|
|
if not projects:
|
|
return "_No inferred strategic projects from the current evidence._\n"
|
|
lines = []
|
|
for p in projects:
|
|
lines.append(
|
|
f"- **{p.project_name}** _({p.status.value.replace('_', ' ')}, confidence {p.confidence:.0%})_"
|
|
)
|
|
lines.append(f" {p.summary}")
|
|
if p.alternative_explanations:
|
|
lines.append(f" Alternative explanations: {'; '.join(p.alternative_explanations)}")
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
def _render_list(items: list[str]) -> str:
|
|
if not items:
|
|
return "_None noted._\n"
|
|
return "\n".join(f"- {item}" for item in items) + "\n"
|
|
|
|
|
|
def render_report_markdown(
|
|
content: ReportContent,
|
|
*,
|
|
company_name: str,
|
|
generated_at: str,
|
|
model_provider: str,
|
|
model_name: str,
|
|
sources: list[dict],
|
|
) -> str:
|
|
parts = [
|
|
f"# Competitive Intelligence Report: {company_name}",
|
|
f"_Generated {generated_at} · {model_provider}/{model_name}_",
|
|
"",
|
|
"## 1. Executive Summary",
|
|
content.executive_summary,
|
|
"",
|
|
"## 2. Company Overview",
|
|
content.company_overview,
|
|
"",
|
|
"## 3. Products and Service Landscape",
|
|
_render_findings(content.products_and_services),
|
|
"## 4. Recent Developments",
|
|
_render_findings(content.recent_developments),
|
|
"## 5. Strategic Initiatives",
|
|
_render_findings(content.strategic_initiatives),
|
|
"## 6. Key Project Signals",
|
|
_render_projects(content.key_inferred_projects),
|
|
"## 7. Competitive Positioning",
|
|
content.market_positioning,
|
|
"",
|
|
content.competitor_comparison,
|
|
"",
|
|
"## 8. SWOT Analysis",
|
|
"**Strengths**",
|
|
_render_list(content.swot.strengths),
|
|
"**Weaknesses**",
|
|
_render_list(content.swot.weaknesses),
|
|
"**Opportunities**",
|
|
_render_list(content.swot.opportunities),
|
|
"**Threats**",
|
|
_render_list(content.swot.threats),
|
|
"## 9. Hiring Signals",
|
|
_render_findings(content.hiring_signals),
|
|
"## 10. Product and Technology Signals",
|
|
_render_findings(content.technology_signals + content.patent_signals),
|
|
"## 11. Customer Sentiment",
|
|
content.customer_sentiment,
|
|
"",
|
|
"## 12. Financial and Regulatory Signals",
|
|
_render_findings(content.financial_signals + content.regulatory_and_legal_signals),
|
|
"## 13. Risks and Opportunities",
|
|
"**Risks**",
|
|
_render_list(content.risks),
|
|
"**Opportunities**",
|
|
_render_list(content.opportunities),
|
|
"## 14. Important Unknowns",
|
|
_render_list(content.unknowns_and_missing_data),
|
|
"## 15. Sources",
|
|
_render_sources(sources),
|
|
"## 16. Methodology and Limitations",
|
|
content.methodology,
|
|
"",
|
|
content.limitations,
|
|
]
|
|
return "\n".join(str(p) for p in parts)
|
|
|
|
|
|
def _render_sources(sources: list[dict]) -> str:
|
|
if not sources:
|
|
return "_No sources recorded for this report._\n"
|
|
lines = [
|
|
f"- [{s.get('title') or s.get('url')}]({s.get('url')}) — retrieved {s.get('retrieved_date')}"
|
|
for s in sources
|
|
]
|
|
return "\n".join(lines) + "\n"
|