Files
CIAgent/apps/api/app/prompts/report_generation.py
T
saksham 1a4c80958f 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).
2026-08-05 10:48:20 -04:00

136 lines
6.2 KiB
Python

"""Task D: report generation. Turns accumulated evidence (source documents +
detected changes) for a company into the structured CI report (spec section
17). The LLM only ever sees evidence this pipeline collected - it is
explicitly instructed not to introduce outside knowledge, and every
non-trivial claim must carry a confidence label and evidence references.
"""
from __future__ import annotations
from pydantic import BaseModel, Field
from app.analysis.llm.base import LLMProvider
from app.prompts.base import build_user_prompt
from app.prompts.schemas import ConfidenceLabel, EvidenceRef, Finding
SYSTEM_PROMPT = (
"You are a competitive intelligence analyst producing a structured report about a "
"company, using ONLY the evidence provided - the company_profile block (discovered once "
"at onboarding from the company's real website and search results, not your own "
"knowledge), the company_enrichment block (when present - fetched once from a paid "
"third-party data provider at onboarding, also real evidence, not your own knowledge), "
"stored source documents, and previously detected changes. Never introduce "
"facts from outside knowledge, even if you recognize the company - if it isn't in the "
"evidence block, it doesn't go in the report. The company_profile and company_enrichment "
"fields ARE real evidence and should ground company_overview/market_positioning/"
"financial_signals/leadership_changes/products_and_services/competitor_comparison/"
"customer_sentiment/etc even when source_documents and detected_changes are sparse or "
"empty - do not say 'insufficient evidence' for a field company_profile or "
"company_enrichment already answers. Every finding must be "
"traceable to the evidence given and must carry an honest confidence label: confirmed "
"(the source states it directly), strongly_indicated, likely, possible, unconfirmed, or "
"insufficient_evidence. When evidence is thin or missing for a section, say so explicitly "
"in that section rather than inventing content. Distinguish clearly between what a source "
"states and what you are inferring."
)
class InferredProject(BaseModel):
project_name: str
status: ConfidenceLabel
summary: str
confidence: float = Field(ge=0.0, le=1.0)
evidence: list[EvidenceRef] = Field(default_factory=list)
signal_types: list[str] = Field(default_factory=list)
alternative_explanations: list[str] = Field(default_factory=list)
class SwotAnalysis(BaseModel):
strengths: list[str] = Field(default_factory=list)
weaknesses: list[str] = Field(default_factory=list)
opportunities: list[str] = Field(default_factory=list)
threats: list[str] = Field(default_factory=list)
class ReportContent(BaseModel):
executive_summary: str
company_overview: str
products_and_services: list[Finding] = Field(default_factory=list)
market_positioning: str
recent_developments: list[Finding] = Field(default_factory=list)
strategic_initiatives: list[Finding] = Field(default_factory=list)
key_inferred_projects: list[InferredProject] = Field(default_factory=list)
leadership_changes: list[Finding] = Field(default_factory=list)
hiring_signals: list[Finding] = Field(default_factory=list)
technology_signals: list[Finding] = Field(default_factory=list)
patent_signals: list[Finding] = Field(default_factory=list)
manufacturing_and_expansion_signals: list[Finding] = Field(default_factory=list)
partnerships_and_acquisitions: list[Finding] = Field(default_factory=list)
financial_signals: list[Finding] = Field(default_factory=list)
regulatory_and_legal_signals: list[Finding] = Field(default_factory=list)
customer_sentiment: str
competitor_comparison: str
swot: SwotAnalysis
risks: list[str] = Field(default_factory=list)
opportunities: list[str] = Field(default_factory=list)
unknowns_and_missing_data: list[str] = Field(default_factory=list)
monitoring_recommendations: list[str] = Field(default_factory=list)
methodology: str
limitations: str
async def generate_report(
llm: LLMProvider,
*,
company_name: str,
company_aliases: list[str],
competitors: list[str],
monitoring_focus: str | None,
industry: str | None,
documents: list[dict],
detected_changes: list[dict],
sources_failed: list[str],
description: str | None = None,
official_website: str | None = None,
headquarters: str | None = None,
country: str | None = None,
region: str | None = None,
public_identifiers: dict[str, str] | None = None,
enrichment: dict | None = None,
) -> ReportContent:
evidence = {
# Discovered once at onboarding (app/services/discovery_service.py)
# from the company's real website + search results - genuine
# evidence, not the LLM's own background knowledge, and the only
# evidence available before any monitoring run has collected
# source_documents/detected_changes.
"company_profile": {
"name": company_name,
"description": description,
"official_website": official_website,
"aliases": company_aliases,
"competitors": competitors,
"monitoring_focus": monitoring_focus,
"industry": industry,
"headquarters": headquarters,
"country": country,
"region": region,
"public_identifiers": public_identifiers or {},
},
# Fetched once, at onboarding, from a paid third-party provider
# (NinjaPear) - see app/services/enrichment_service.py. Absent for
# any company created before this feature existed, or whose
# enrichment never completed successfully - never fabricated.
"company_enrichment": enrichment or {},
"source_documents": documents,
"detected_changes": detected_changes,
"sources_that_failed_to_collect": sources_failed,
}
user_prompt = build_user_prompt(
"Produce a full structured competitive intelligence report from this evidence. "
"If a section has no supporting evidence, say so explicitly instead of leaving it "
"generic or inventing content.",
evidence,
)
return await llm.generate_structured(SYSTEM_PROMPT, user_prompt, ReportContent)