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
+44
View File
@@ -0,0 +1,44 @@
from __future__ import annotations
import uuid
from typing import Any
from sqlalchemy import JSON, Enum, Float, ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
from app.models.enums import ChangeStatus, ChangeType, SeverityLevel
class DetectedChange(Base, UUIDPrimaryKeyMixin, TimestampMixin):
__tablename__ = "detected_changes"
company_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey("companies.id", ondelete="CASCADE"), index=True
)
source_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey("sources.id", ondelete="CASCADE"), index=True
)
monitoring_run_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey("monitoring_runs.id", ondelete="CASCADE"), index=True
)
previous_snapshot_id: Mapped[uuid.UUID | None] = mapped_column(
ForeignKey("snapshots.id", ondelete="SET NULL"), nullable=True
)
current_snapshot_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey("snapshots.id", ondelete="CASCADE")
)
change_type: Mapped[ChangeType] = mapped_column(Enum(ChangeType, native_enum=False, length=30))
raw_diff: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
significance_score: Mapped[float] = mapped_column(Float)
confidence_score: Mapped[float] = mapped_column(Float)
severity: Mapped[SeverityLevel] = mapped_column(
Enum(SeverityLevel, native_enum=False, length=20)
)
status: Mapped[ChangeStatus] = mapped_column(
Enum(ChangeStatus, native_enum=False, length=20), default=ChangeStatus.NEW
)
# Short human-readable label, e.g. "3 new job postings detected" -
# populated deterministically here; Phase 7's LLM may later add a
# richer "why it matters" narrative on top without replacing this.
summary: Mapped[str] = mapped_column(String(500))