New alert system and panel de control page
This commit is contained in:
181
services/orchestrator/app/api/internal.py
Normal file
181
services/orchestrator/app/api/internal.py
Normal file
@@ -0,0 +1,181 @@
|
||||
"""
|
||||
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"),
|
||||
x_internal_service: str = Header(None, description="Internal service authentication")
|
||||
):
|
||||
"""
|
||||
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()
|
||||
|
||||
# Simple internal service authentication
|
||||
if x_internal_service != "alert-intelligence":
|
||||
raise HTTPException(status_code=403, detail="Access denied")
|
||||
|
||||
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"}
|
||||
Reference in New Issue
Block a user