178 lines
7.1 KiB
Python
178 lines
7.1 KiB
Python
"""
|
|
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"}
|