"""Shared HTTP/MCP facade for one subscription's explicit AI analysis.""" from __future__ import annotations import uuid from collections.abc import Iterable from typing import Any from sqlalchemy.ext.asyncio import AsyncSession from app.intelligence.cloud_scope_analysis import ( generate_cloud_scope_analysis, load_cloud_scope_analysis_history, load_cloud_scope_analysis_state, require_cloud_scope_analysis_access, ) from app.intelligence.cloud_scope_projection import ( project_cloud_scope_analysis_history, project_cloud_scope_analysis_state, ) from app.intelligence.cloud_scope_snapshot import build_cloud_scope_snapshot from app.intelligence.settings import ( apply_intelligence_read_limit, require_intelligence_enabled, ) async def get_cloud_scope_analysis_public( db: AsyncSession, *, scope_id: str, person_id: uuid.UUID, role_names: Iterable[str], ) -> dict[str, Any]: """Return shared state, preparing a missing deterministic baseline locally. This deliberately remains model-free: it only reads the subscription's already-authorized source facts and writes the same immutable shared snapshot the daily baseline job would have prepared. A subscription must therefore never look empty merely because the background pre-warm worker has not run yet. Narrative generation stays an explicit POST mutation. """ await require_intelligence_enabled(db) access = await require_cloud_scope_analysis_access( db, scope_id=scope_id, person_id=person_id, role_names=role_names, ) await apply_intelligence_read_limit(db, user_id=person_id) state = await load_cloud_scope_analysis_state(db, access=access) if state.snapshot is None: # ``build_cloud_scope_snapshot`` uses an immutable facts-hash identity # and commits its own narrow unit of work. Concurrent first readers # safely converge on one row; it cannot call Bailian or spend tokens. await build_cloud_scope_snapshot(db, str(access.scope.id)) state = await load_cloud_scope_analysis_state(db, access=access) return await project_cloud_scope_analysis_state( db, state=state, person_id=person_id, ) async def get_cloud_scope_analysis_history_public( db: AsyncSession, *, scope_id: str, person_id: uuid.UUID, role_names: Iterable[str], limit: int = 50, ) -> dict[str, Any]: """Feature-gated bounded history with the same access and read budget.""" await require_intelligence_enabled(db) access = await require_cloud_scope_analysis_access( db, scope_id=scope_id, person_id=person_id, role_names=role_names, ) await apply_intelligence_read_limit(db, user_id=person_id) rows = await load_cloud_scope_analysis_history(db, access=access, limit=limit) return await project_cloud_scope_analysis_history(db, access=access, rows=rows) async def generate_cloud_scope_analysis_public( db: AsyncSession, *, scope_id: str, person_id: uuid.UUID, role_names: Iterable[str], force: bool = False, confirm_force: bool = False, ) -> dict[str, Any]: """The only public cost-producing path; every call is explicit.""" await require_intelligence_enabled(db) result = await generate_cloud_scope_analysis( db, scope_id=scope_id, person_id=person_id, role_names=role_names, force=force, confirm_force=confirm_force, ) # Generation can commit and refresh the immutable snapshot. Resolve the # current authority again rather than retaining a possibly stale ORM row. access = await require_cloud_scope_analysis_access( db, scope_id=scope_id, person_id=person_id, role_names=role_names, ) state = await load_cloud_scope_analysis_state(db, access=access) return await project_cloud_scope_analysis_state( db, state=state, person_id=person_id, request_cache_reused=result.cache_reused, ) __all__ = [ "generate_cloud_scope_analysis_public", "get_cloud_scope_analysis_history_public", "get_cloud_scope_analysis_public", ]