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
@@ -0,0 +1,173 @@
from __future__ import annotations
import uuid
from datetime import datetime
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.models.company import Company, CompanyAlias, Competitor
from app.models.monitor_configuration import MonitorConfiguration
def _with_relations(stmt):
return stmt.options(
selectinload(Company.aliases),
selectinload(Company.competitors),
selectinload(Company.monitor_configuration),
selectinload(Company.enrichment),
# Loaded eagerly so ORM-level cascade-delete (SQLite doesn't enforce
# FK ON DELETE CASCADE without a pragma this app doesn't set) can
# actually see the children to remove when a Company is deleted.
selectinload(Company.notification_links),
)
class CompanyRepository:
def __init__(self, db: AsyncSession) -> None:
self.db = db
async def get_for_user(self, company_id: uuid.UUID, user_id: uuid.UUID) -> Company | None:
stmt = _with_relations(
select(Company).where(Company.id == company_id, Company.user_id == user_id)
)
result = await self.db.execute(stmt)
return result.scalar_one_or_none()
async def get_by_id(self, company_id: uuid.UUID) -> Company | None:
"""Unscoped lookup for trusted internal callers (Celery tasks) that
already have the company_id from a source they control - not for
anything reachable from an HTTP request without an ownership check."""
stmt = _with_relations(select(Company).where(Company.id == company_id))
result = await self.db.execute(stmt)
return result.scalar_one_or_none()
async def list_for_user(self, user_id: uuid.UUID) -> list[Company]:
stmt = _with_relations(
select(Company).where(Company.user_id == user_id).order_by(Company.created_at.desc())
)
result = await self.db.execute(stmt)
return list(result.scalars().all())
async def count_for_user(self, user_id: uuid.UUID) -> int:
result = await self.db.execute(
select(func.count()).select_from(Company).where(Company.user_id == user_id)
)
return int(result.scalar_one())
async def slug_exists_for_user(self, user_id: uuid.UUID, slug: str) -> bool:
result = await self.db.execute(
select(func.count())
.select_from(Company)
.where(Company.user_id == user_id, Company.slug == slug)
)
return int(result.scalar_one()) > 0
async def name_exists_for_user(self, user_id: uuid.UUID, name: str) -> bool:
result = await self.db.execute(
select(func.count())
.select_from(Company)
.where(Company.user_id == user_id, func.lower(Company.name) == name.lower())
)
return int(result.scalar_one()) > 0
async def create(
self,
*,
user_id: uuid.UUID,
name: str,
slug: str,
official_website: str | None,
description: str | None,
monitoring_focus: str | None,
industry: str | None,
country: str | None,
region: str | None,
headquarters: str | None = None,
public_identifiers: dict[str, str] | None = None,
alias_names: list[str],
competitor_names: list[str],
) -> Company:
company = Company(
user_id=user_id,
name=name,
slug=slug,
official_website=official_website,
description=description,
monitoring_focus=monitoring_focus,
industry=industry,
country=country,
region=region,
headquarters=headquarters,
public_identifiers=public_identifiers or {},
)
company.aliases = [CompanyAlias(alias=a) for a in alias_names if a.strip()]
company.competitors = [Competitor(name=c) for c in competitor_names if c.strip()]
self.db.add(company)
await self.db.flush()
return company
async def delete(self, company: Company) -> None:
await self.db.delete(company)
await self.db.flush()
async def replace_aliases(self, company: Company, alias_names: list[str]) -> None:
company.aliases = [CompanyAlias(alias=a) for a in alias_names if a.strip()]
async def replace_competitors(self, company: Company, competitor_names: list[str]) -> None:
company.competitors = [Competitor(name=c) for c in competitor_names if c.strip()]
class MonitorConfigurationRepository:
def __init__(self, db: AsyncSession) -> None:
self.db = db
async def create_default(
self,
*,
company_id: uuid.UUID,
frequency_type,
interval_minutes: int | None,
cron_expression: str | None,
timezone: str,
severity_threshold,
next_run,
) -> MonitorConfiguration:
config = MonitorConfiguration(
company_id=company_id,
frequency_type=frequency_type,
interval_minutes=interval_minutes,
cron_expression=cron_expression,
timezone=timezone,
severity_threshold=severity_threshold,
next_run=next_run,
)
self.db.add(config)
await self.db.flush()
return config
async def list_due(self, now: datetime) -> list[MonitorConfiguration]:
"""Enabled schedules whose next_run has arrived - what Celery Beat's
sync_schedules task polls instead of requiring a static per-company
beat_schedule entry (see ARCHITECTURE.md)."""
result = await self.db.execute(
select(MonitorConfiguration).where(
MonitorConfiguration.enabled.is_(True),
MonitorConfiguration.next_run.is_not(None),
MonitorConfiguration.next_run <= now,
)
)
return list(result.scalars().all())
async def list_enabled(self) -> list[MonitorConfiguration]:
"""Every enabled schedule regardless of next_run - since a company
can now also become due purely via a per-source frequency override
even when its own default next_run isn't due yet (see
SourceRepository.list_due_for_company), the scheduler needs this
broader set to check, not just the ones already due on the
company-level clock."""
result = await self.db.execute(
select(MonitorConfiguration).where(MonitorConfiguration.enabled.is_(True))
)
return list(result.scalars().all())