"""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)