""" Internal API for Alert Intelligence Service Provides orchestrator context for alert enrichment """ from fastapi import APIRouter, Header, HTTPException, Query from typing import Optional, List, Dict, Any from datetime import datetime, timedelta from uuid import UUID from pydantic import BaseModel router = APIRouter(prefix="/api/internal", tags=["internal"]) class OrchestrationAction(BaseModel): """Recent orchestration action""" id: str type: str # purchase_order, production_batch status: str # created, pending_approval, approved, completed delivery_date: Optional[datetime] reasoning: Optional[Dict[str, Any]] estimated_resolution: Optional[datetime] created_at: datetime class RecentActionsResponse(BaseModel): """Response with recent orchestrator actions""" actions: List[OrchestrationAction] count: int @router.get("/recent-actions", response_model=RecentActionsResponse) async def get_recent_actions( tenant_id: str = Query(..., description="Tenant ID"), ingredient_id: Optional[str] = Query(None, description="Filter by ingredient"), product_id: Optional[str] = Query(None, description="Filter by product"), hours_ago: int = Query(24, description="Look back hours"), ): """ Get recent orchestrator actions for alert context enrichment. Only accessible by internal services (alert-intelligence). Returns orchestration runs with details about POs created, batches adjusted, etc. This helps the alert system understand if AI already addressed similar issues. """ from shared.database.base import create_database_manager from ..core.config import get_settings from ..models.orchestration_run import OrchestrationRun, OrchestrationStatus from sqlalchemy import select, and_, desc import structlog logger = structlog.get_logger() try: settings = get_settings() db_manager = create_database_manager(settings.DATABASE_URL, "orchestrator") async with db_manager.get_session() as session: cutoff_time = datetime.utcnow() - timedelta(hours=hours_ago) # Query recent orchestration runs query = select(OrchestrationRun).where( and_( OrchestrationRun.tenant_id == UUID(tenant_id), OrchestrationRun.created_at >= cutoff_time, OrchestrationRun.status.in_([ OrchestrationStatus.completed, OrchestrationStatus.partial_success ]) ) ).order_by(desc(OrchestrationRun.created_at)) result = await session.execute(query) runs = result.scalars().all() actions = [] for run in runs: run_metadata = run.run_metadata or {} # Add purchase order actions if run.purchase_orders_created > 0: po_details = run_metadata.get('purchase_orders', []) # If metadata has PO details, use them if po_details: for po in po_details: # Filter by ingredient if specified if ingredient_id: po_items = po.get('items', []) has_ingredient = any( item.get('ingredient_id') == ingredient_id for item in po_items ) if not has_ingredient: continue actions.append(OrchestrationAction( id=po.get('id', str(run.id)), type="purchase_order", status=po.get('status', 'created'), delivery_date=po.get('delivery_date'), reasoning=run_metadata.get('reasoning'), estimated_resolution=po.get('delivery_date'), created_at=run.created_at )) else: # Fallback: create generic action from run actions.append(OrchestrationAction( id=str(run.id), type="purchase_order", status="created", delivery_date=None, reasoning=run_metadata.get('reasoning'), estimated_resolution=None, created_at=run.created_at )) # Add production batch actions if run.production_batches_created > 0: batch_details = run_metadata.get('production_batches', []) if batch_details: for batch in batch_details: # Filter by product if specified if product_id and batch.get('product_id') != product_id: continue actions.append(OrchestrationAction( id=batch.get('id', str(run.id)), type="production_batch", status=batch.get('status', 'created'), delivery_date=None, reasoning=run_metadata.get('reasoning'), estimated_resolution=batch.get('scheduled_date'), created_at=run.created_at )) else: # Fallback: create generic action from run if not product_id: # Only add if no product filter actions.append(OrchestrationAction( id=str(run.id), type="production_batch", status="created", delivery_date=None, reasoning=run_metadata.get('reasoning'), estimated_resolution=None, created_at=run.created_at )) logger.info( "recent_actions_fetched", tenant_id=tenant_id, hours_ago=hours_ago, action_count=len(actions), ingredient_id=ingredient_id, product_id=product_id ) return RecentActionsResponse( actions=actions, count=len(actions) ) except Exception as e: logger.error("error_fetching_recent_actions", error=str(e), tenant_id=tenant_id) raise HTTPException( status_code=500, detail=f"Failed to fetch recent actions: {str(e)}" ) @router.get("/health") async def internal_health(): """Internal health check""" return {"status": "healthy", "api": "internal"}