PRISM

Modules/Intel-Company.md

Module: intel-company

THE FOUNDATION HUB. Runs first in every audit. All spoke modules depend on its output. If this module fails, the entire audit aborts.


Identity

Field Value
Name intel-company
Version 0.2.0
Layer Intelligence (Wave 1)
Wave 1 (runs first, all others wait for it)
Description Foundation company intelligence via single Perplexity API call. Populates accounts table with company identity, executives, competitors, financials, and field-level source citations.
LLM Tier None (deterministic JSON parsing, no LLM enrichment)
Timeout 120 seconds
Max Retries 2 (module level) + 3 (collector level)
Gate Module YES. Failure aborts entire audit.

Dependencies

None. This is the foundation module. All other modules depend on it.

Hub-and-Spoke: What Each Spoke Reads

intel-company writes to the accounts table. Every spoke module reads specific columns from that table. This is the complete dependency map:

Spoke Module Reads Why
intel-traffic Account.competitors Runs SimilarWeb on each competitor for comparative traffic
intel-hiring Account.executives, Account.competitors Exec names for buying committee; competitor domains for comparative hiring
intel-news Account.executives, Account.competitors Exec-specific news search; competitor news tracking
intel-social Account.executives, Account.competitors Exec LinkedIn/Twitter activity; competitor social signals
intel-investor executives (via context), competitor domains Exec names for earnings quote attribution; competitor tickers for financial comparison
intel-partner Account.industry, Account.competitors Industry for vertical SI relationships; competitor domains for partner overlap
intel-industry Account.industry, Account.sub_vertical Vertical classification for benchmarks, trends, pain points
intel-techstack Account.competitors (if populated) Fan-out: runs BuiltWith on each competitor domain
intel-queries Account.competitors, Account.product_categories Product categories for vertically-calibrated test queries; competitor domains for competitor query sets
intel-financial-public ticker (via ExecutionContext) Pulls Yahoo Finance data for the prospect ticker
intel-financial-private company_name, industry, employee_count Revenue waterfall estimation using company context
intel-competitors ALL upstream outputs via module_executions Pure synthesis. Depends on intel-company for the competitor list itself.
audit-browser has_search_bar Search bar detection tells browser module WHERE to look

Why It's the Gate

If intel-company fails: - No competitors = traffic, techstack, competitors modules have nothing to compare - No executives = hiring, social, news modules can't find exec-specific intelligence - No industry = industry, partner modules can't find vertical context - No ticker = financial-public can't pull Yahoo Finance data - No product_categories = queries module can't generate vertically-calibrated tests

Every spoke is structurally dependent on at least one field that only intel-company provides.


Input Schema

Field Type Required Description
domain str yes Website domain to analyze, e.g. "dell.com"

Output Schema: CompanyProfileOutput

Identity Fields

Field Type Required Description
legal_name str yes Official registered company name
common_name str yes Name used in press/marketing
domain str yes Primary website domain
headquarters str yes HQ city and country, e.g. "Round Rock, Texas, USA"
employee_count int or None no Approximate headcount as integer (e.g. 133000)
employee_count_source str or None no Where count came from (e.g. "LinkedIn")
year_founded int or None no 4-digit year (e.g. 1984)
business_model str yes 3+ sentences: what they do, how they make money, who their customers are. Min 50 chars.
motto str or None no Company tagline if publicly stated

Classification Fields

Field Type Required Description
industry str yes Primary industry (e.g. "Enterprise Technology")
sub_vertical str or None no Specific sub-vertical (e.g. "Consumer Electronics")
is_public bool yes True if publicly traded
ticker str or None no Stock ticker if public (e.g. "DELL"). None if private.
parent_company str or None no Parent company if subsidiary. None if independent.
revenue_estimate float or None no Annual revenue in USD as float (e.g. 88400000000.0)
revenue_source str or None no Source of revenue figure (e.g. "SEC 10-K FY2025")

People and Competitors

Field Type Required Description
executives list[Executive] yes 8-12 key executives. Min 3 for validation pass.
competitors list[Competitor] yes 5-7 direct competitors with domains. Min 3 for validation.
recent_news list[NewsItem] no 3-10 news items from last 90 days
recent_blog_posts list[BlogPost] no 3-5 posts from company blog/newsroom

Website Snapshot

Field Type Required Description
has_search_bar bool or None no Whether homepage has a visible search bar (detected via HTML regex)
product_categories list[str] no Top-level product/service categories
search_experience_description str or None no Brief description of current search UX

Sub-Schema: Executive

Field Type Required Description
full_name str yes Full name
title str yes Current job title
linkedin_url str or None no LinkedIn profile URL (must be real, not fabricated)
headshot_url str or None no URL to headshot image
tenure_description str or None no How long in role (e.g. "Since 2019")
previous_company str or None no Most recent previous employer
previous_role str or None no Title at previous company

Sub-Schema: Competitor

Field Type Required Description
company_name str yes Competitor name
domain str yes Primary website domain
why_competitor str yes One sentence: why they compete
is_algolia_customer bool no True if in KNOWN_ALGOLIA_CUSTOMERS set (Golden Angle)

Sub-Schema: NewsItem

Field Type Required Description
headline str yes Article headline
source str yes Publication name
date str yes YYYY-MM-DD format
url str or None no Article URL
category str no One of: leadership_change, product_launch, partnership, financial, acquisition, technology, expansion, bankruptcy, other

Sub-Schema: BlogPost

Field Type Required Description
title str yes Post title
date str yes YYYY-MM-DD format
url str or None no Post URL
summary str no One-sentence summary

Collection Strategy

Source 1: Perplexity API (sonar model)

Aspect Detail
Client AgentAPIClient (httpx, Bearer token)
Endpoint POST https://api.perplexity.ai/chat/completions
Model sonar-pro ($3/M input tokens)
Input Resolved playbook.md — system prompt from ModuleConfig, user prompt from playbook
Output JSON string validated against CompanySeedOutput schema
Retry executor.max_retries = 2
Evidence Tier WEBSEARCH (Perplexity searches the web)
Temperature 0.1 (near-deterministic)
Max Tokens 8192

Prompt strategy: The prompt is highly specific. It tells Perplexity exactly where to look for executives (LinkedIn, About Us page, Companies House, press releases). It demands minimums (5+ executives, 5+ competitors). It provides the exact JSON schema with example values. It specifies format rules (revenue as raw number, dates as YYYY-MM-DD, no fabricated LinkedIn URLs).

Source 2: Homepage HTML Fetch

Aspect Detail
Removed in v2. Homepage fetch for search bar detection is deferred to audit-browser (Wave 2), which does live Playwright testing.

The is_algolia_customer golden-angle signal is now detected via golden_angle_competitors in intel-techstack.


Enrichment Strategy

Aspect Detail
Method ModuleExecutor (generic v2 harness)
LLM Model sonar-pro via AgentAPIClient
Pipeline PlaybookLoader → resolve template → AgentAPIClient.research() → strip_code_fences() → json.loads() → CompanySeedOutput.model_validate() → auto-generate ClaimRegistryEntry list

Perplexity returns JSON. The executor strips any markdown fences, parses, and validates against the Pydantic schema. Claims are auto-generated from every output field that has a citation.

Citation Extraction

Perplexity annotates facts with inline citations in two formats: 1. [label](url) -- named citations. Extracted via regex. 2. [1][2] -- numbered citations. Stripped from field values to prevent "SHOP[1][2]" type corruption.

Citations become Source records with tier=WEBSEARCH and method=llm_extraction.

Post-Parse Processing

  1. Search bar injection -- has_search_bar from homepage fetch merged into output.
  2. Algolia customer cross-check -- Each competitor.domain checked against KNOWN_ALGOLIA_CUSTOMERS set. If match, is_algolia_customer = True (Golden Angle detection).

Validation Rules (8 Checks)

# Rule Severity Threshold
1 legal_name is populated required Not empty string
2 domain matches expected input required Exact case-insensitive match
3 headquarters is populated required Not empty string
4 Sufficient executives found required >= 3 executives
5 Sufficient competitors found required >= 3 competitors
6 At least 1 exec has LinkedIn URL warning Any exec with linkedin_url
7 business_model is substantive required >= 50 characters
8 At least 1 news item warning recent_news not empty

Required failures -> status "partial". Warning failures -> logged but passed.


Evidence Requirements

Field Minimum Tier Rationale
legal_name WEBSEARCH Perplexity synthesis is sufficient for company name
headquarters WEBSEARCH Perplexity synthesis is sufficient
executives WEBSEARCH Perplexity aggregates from LinkedIn + company pages
competitors WEBSEARCH Perplexity competitive research
revenue_estimate ESTIMATE Acceptable for initial context; financial modules refine later
has_search_bar WEBFETCH Direct HTML fetch, not search-based
ticker WEBSEARCH Cross-verified by intel-financial-public later

Persistence (Hub -> Accounts Table)

intel-company is unique: it writes to the accounts table, not just module_executions. This is because spoke modules read from accounts columns directly.

Columns Written

legal_name, company_name, headquarters, employee_count, employee_count_source,
year_founded, business_model, motto, industry, sub_vertical, is_public, ticker,
parent_company, revenue_estimate, revenue_source, has_search_bar,
product_categories,   # list[str] -> JSONB
executives,           # list[Executive] -> JSONB array
competitors,          # list[Competitor] -> JSONB array
recent_news,          # list[NewsItem] -> JSONB array
recent_blog_posts,    # list[BlogPost] -> JSONB array
sources,              # list[citation dict] -> JSONB array
updated_at            # timestamp

Cost Profile

Metric Expected Value
LLM Calls 1 (Perplexity sonar)
Estimated Cost ~$0.01-0.03 (sonar is cheap)
Expected Duration 5-15 seconds
External API Calls 2 (Perplexity + homepage fetch)

Retry Architecture (Two Layers)

Layer 1: Collector Retries (network errors)

Trigger Retry Backoff
Timeout 3 attempts Exponential (2^attempt seconds)
Rate limit (429) 3 attempts Exponential
Connection error 3 attempts Exponential
Other HTTP error No retry Fail immediately

Layer 2: Module Retries (data quality)

Trigger Retry Backoff
Empty Perplexity response 3 attempts 5 seconds fixed
JSON parse failure 3 attempts 5 seconds fixed
Pydantic validation failure 3 attempts 5 seconds fixed

If all retries exhausted: return ModuleExecutorResult with status="failed".


Runtime Notes

SaaS Runtime (Temporal/Python)

  • Config: prism_platform/v2/modules/intel_company/config.py — INTEL_COMPANY_CONFIG (ModuleConfig)
  • Playbook: prism_platform/v2/modules/intel_company/playbook.md — research instructions
  • Schema: prism_platform/v2/modules/intel_company/schemas.py — CompanySeedOutput
  • Hooks: prism_platform/v2/modules/intel_company/hooks.py — intel_company_post_execute (accounts table write)
  • Registry: registered in prism_platform/v2/registry.py as ModuleHandle with post_execute hook
  • Harness: prism_platform/v2/executor.py — ModuleExecutor (generic, shared)
  • Persistence: accounts table (via post_execute hook, keyed by domain) + module_executions table (via Temporal activity)

Skill Runtime (Claude Code)

  • Skill file: ~/.claude/skills/algolia-intel-company/SKILL.md
  • Reads from: nothing (foundation module)
  • Writes to: 01-company-context.md, 01-company-context.json
  • MCP tools used: WebSearch (for Perplexity-equivalent research), WebFetch (homepage)

What Makes This Production-Grade (vs a Simple Prompt)

Aspect Simple Prompt PRISM Module
Data collection "Research this company" Specific composite prompt with field-level instructions, minimums, format rules
Structuring Hope the LLM returns good JSON Deterministic parser: extract citations, strip noise, json.loads(), Pydantic validate
Retry logic None Two layers: collector (network) + module (data quality). 3 attempts each.
Source provenance None Every fact tracked to source URL + evidence tier
Search bar detection Ask the LLM to guess Fetch actual HTML, run 5 regex patterns. Deterministic.
Golden Angle Not detected Competitor domains cross-checked against KNOWN_ALGOLIA_CUSTOMERS
Persistence Write a text file Normalized columns on accounts table; JSONB for structured arrays
Validation Trust the LLM 8-point validation checklist (6 required, 2 warning)
Downstream value Text blob another prompt reads Typed columns that spoke modules query directly

Changelog

Date Change Reason
2026-03-30 Initial implementation Session 1: backend phase 1
2026-04-02 Denormalized accounts table Migration 005: moved from intelligence JSONB to proper columns
2026-04-08 Module spec written to vault Knowledge extraction for skill refactoring

  • Module-Catalog -- all 20 modules overview
  • Module-Spec-Template -- the template this spec follows
  • Evidence-Tier-Spec -- evidence tier system
  • Adapter-Interfaces -- PerplexityAdapter and WebFetchAdapter