Files
CIAgent/apps/api/app/services/monitoring_service.py
T
saksham 1a4c80958f 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).
2026-08-05 10:48:20 -04:00

67 lines
2.5 KiB
Python

"""Run-now / run-history service. Enqueuing goes through Celery
(`run_monitoring.delay`); this module only ever touches the `MonitoringRun`
row and ownership checks - the actual collection work happens in the task
(app/tasks/collection.py) and collection_service.
"""
from __future__ import annotations
import uuid
from datetime import UTC, datetime, timedelta
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import Settings
from app.core.errors import NotFoundError, RateLimitedError
from app.models.enums import MonitoringRunTrigger
from app.models.monitoring_run import MonitoringRun
from app.repositories.monitoring_run_repository import MonitoringRunRepository
from app.services import company_service
async def enqueue_run_now(
db: AsyncSession, settings: Settings, user_id: uuid.UUID, company_id: uuid.UUID
) -> MonitoringRun:
company = await company_service.get_company(db, user_id, company_id)
run_repo = MonitoringRunRepository(db)
# Idempotent: a company already mid-run returns that run rather than
# queuing a duplicate (spec: "unique job keys to prevent duplicate
# concurrent runs").
active = await run_repo.get_active_for_company(company.id)
if active is not None:
return active
since = datetime.now(UTC) - timedelta(days=1)
manual_count = await run_repo.count_manual_since(company.id, since)
if manual_count >= settings.max_manual_runs_per_day:
raise RateLimitedError(
f"This company has reached the maximum of {settings.max_manual_runs_per_day} "
"manual runs per day. Scheduled runs are unaffected."
)
run = await run_repo.create(company_id=company.id, trigger_type=MonitoringRunTrigger.MANUAL)
await db.commit()
from app.tasks.collection import (
run_monitoring,
) # local import: keeps Celery out of API startup path
run_monitoring.delay(str(run.id))
return run
async def list_runs(
db: AsyncSession, user_id: uuid.UUID, company_id: uuid.UUID
) -> list[MonitoringRun]:
await company_service.get_company(db, user_id, company_id)
return await MonitoringRunRepository(db).list_for_company(company_id)
async def get_run(db: AsyncSession, user_id: uuid.UUID, run_id: uuid.UUID) -> MonitoringRun:
run = await MonitoringRunRepository(db).get(run_id)
if run is None:
raise NotFoundError("Monitoring run not found")
await company_service.get_company(db, user_id, run.company_id) # ownership check
return run