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:
2026-08-05 10:48:20 -04:00
commit 1a4c80958f
365 changed files with 43541 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
"""Company-profile discovery extraction. Used once per company, at
onboarding time (see app/services/discovery_service.py) - never asked
"tell me everything about this company," only "given this fetched
homepage text and these search snippets, extract what's actually
supported." Leaves a field unset rather than guessing when the evidence
doesn't support it; the wizard's Review step shows unset fields as
"not found" for the user to fill in themselves.
"""
from __future__ import annotations
from pydantic import BaseModel, Field
from app.analysis.llm.base import LLMProvider
from app.prompts.base import build_user_prompt
SYSTEM_PROMPT = (
"You are a competitive intelligence analyst building an initial company profile from "
"search results and a fetched homepage. Extract only what the evidence actually states or "
"clearly implies - never use outside knowledge, never guess. Leave a field null (or an "
"empty list) rather than filling it with a plausible-sounding guess. Aliases means other "
"names the company is or was known by (former names, common abbreviations, brand names) - "
"not synonyms or descriptions. Competitors means other named companies the evidence "
"explicitly identifies as competing in the same space."
)
class PublicIdentifier(BaseModel):
key: str = Field(description="e.g. 'ticker', 'linkedin_url', 'cik'")
value: str
class CompanyProfileExtraction(BaseModel):
description: str | None = Field(
default=None,
description="A short (1-3 sentence) factual summary of what the company does, drawn "
"strictly from the evidence - not a marketing tagline.",
)
industry: str | None = None
country: str | None = None
region: str | None = None
headquarters: str | None = None
aliases: list[str] = Field(default_factory=list)
competitors: list[str] = Field(default_factory=list)
public_identifiers: list[PublicIdentifier] = Field(
default_factory=list,
description="Best-effort key/value pairs, e.g. ticker or linkedin_url - empty if none found. "
"A list of {key, value} pairs rather than a free-form object, since the Gemini Developer "
"API's structured-output mode rejects open-ended (additionalProperties) JSON schemas.",
)
async def extract_company_profile(
llm: LLMProvider,
*,
company_name: str,
homepage_url: str | None,
homepage_text: str | None,
search_results: list[dict],
) -> CompanyProfileExtraction:
evidence = {
"company_name": company_name,
"homepage_url": homepage_url,
"homepage_text": (homepage_text or "")[:4000],
"search_results": search_results[:15],
}
user_prompt = build_user_prompt(
"Build an initial company profile (description, industry, country, region, "
"headquarters, aliases, competitors, public identifiers) strictly from this evidence.",
evidence,
)
return await llm.generate_structured(SYSTEM_PROMPT, user_prompt, CompanyProfileExtraction)