Initial commit: CI Agent competitive-intelligence monitoring app
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).
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user