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
+93
View File
@@ -0,0 +1,93 @@
"""Company-enrichment provider interface. Answers "what does a paid,
first-party data vendor already know about this company?" - a richer,
metered complement to the free SearchProvider/LLM discovery pipeline
(app/services/discovery_service.py), never a replacement for it. Every
method here maps to one NinjaPear (nubela.co) API endpoint; orchestration
(call order, the leadership-lookup cap, partial-failure handling) lives in
app/services/enrichment_service.py, not here - this Protocol is a dumb I/O
layer, matching app/search/base.py's shape.
"""
from __future__ import annotations
from typing import Protocol
from pydantic import BaseModel, Field
class LeadershipMember(BaseModel):
name: str
title: str | None = None
work_email: str | None = None
profile_url: str | None = None
bio: str | None = None
class CompanyDetails(BaseModel):
description: str | None = None
industry: str | None = None
founded_year: int | None = None
specialties: list[str] = Field(default_factory=list)
leadership_team: list[LeadershipMember] = Field(default_factory=list)
employee_count_range: str | None = None
class FundingRound(BaseModel):
round_name: str | None = None
amount: str | None = None
date: str | None = None
investors: list[str] = Field(default_factory=list)
class CompanyFunding(BaseModel):
total_raised: str | None = None
rounds: list[FundingRound] = Field(default_factory=list)
class CompetitorWithReason(BaseModel):
name: str
reason: str | None = None
class Product(BaseModel):
name: str
description: str | None = None
category: str | None = None
class RecentUpdate(BaseModel):
type: str
text: str
url: str | None = None
date: str | None = None
class Customer(BaseModel):
name: str
relationship: str | None = None
class EnrichmentProvider(Protocol):
provider_name: str
async def get_company_details(self, name: str, website: str | None) -> CompanyDetails: ...
async def get_funding(self, name: str, website: str | None) -> CompanyFunding: ...
async def get_updates(self, name: str, website: str | None) -> list[RecentUpdate]: ...
async def get_competitors(
self, name: str, website: str | None
) -> list[CompetitorWithReason]: ...
async def get_products(self, name: str, website: str | None) -> list[Product]: ...
async def get_customers(self, name: str, website: str | None) -> list[Customer]: ...
async def get_work_email(self, person_name: str, company_website: str) -> str | None: ...
async def get_person_profile(
self, person_name: str, company_website: str
) -> tuple[str | None, str | None]:
"""Returns (profile_url, bio)."""
...