Add AI insights feature

This commit is contained in:
Urtzi Alfaro
2025-12-15 21:14:22 +01:00
parent 5642b5a0c0
commit c566967bea
39 changed files with 17729 additions and 404 deletions

View File

@@ -14,6 +14,7 @@ import os
# Add shared clients to path
sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
from shared.clients.ai_insights_client import AIInsightsClient
from shared.messaging import UnifiedEventPublisher
from app.ml.safety_stock_optimizer import SafetyStockOptimizer
@@ -28,15 +29,18 @@ class SafetyStockInsightsOrchestrator:
1. Optimize safety stock from demand history and cost parameters
2. Generate insights comparing optimal vs hardcoded approach
3. Post insights to AI Insights Service
4. Provide optimized safety stock levels for inventory management
4. Publish recommendation events to RabbitMQ
5. Provide optimized safety stock levels for inventory management
"""
def __init__(
self,
ai_insights_base_url: str = "http://ai-insights-service:8000"
ai_insights_base_url: str = "http://ai-insights-service:8000",
event_publisher: Optional[UnifiedEventPublisher] = None
):
self.optimizer = SafetyStockOptimizer()
self.ai_insights_client = AIInsightsClient(ai_insights_base_url)
self.event_publisher = event_publisher
async def optimize_and_post_insights(
self,
@@ -109,6 +113,17 @@ class SafetyStockInsightsOrchestrator:
successful=post_results['successful'],
failed=post_results['failed']
)
# Step 4: Publish recommendation events to RabbitMQ
created_insights = post_results.get('created_insights', [])
if created_insights:
product_context = product_characteristics.copy() if product_characteristics else {}
product_context['inventory_product_id'] = inventory_product_id
await self._publish_insight_events(
tenant_id=tenant_id,
insights=created_insights,
product_context=product_context
)
else:
post_results = {'total': 0, 'successful': 0, 'failed': 0}
logger.info("No insights to post for product", inventory_product_id=inventory_product_id)
@@ -167,6 +182,84 @@ class SafetyStockInsightsOrchestrator:
return enriched
async def _publish_insight_events(
self,
tenant_id: str,
insights: List[Dict[str, Any]],
product_context: Optional[Dict[str, Any]] = None
) -> None:
"""
Publish recommendation events to RabbitMQ for each insight.
Args:
tenant_id: Tenant identifier
insights: List of created insights (with insight_id from AI Insights Service)
product_context: Optional product context (name, id, etc.)
"""
if not self.event_publisher:
logger.warning("Event publisher not configured, skipping event publication")
return
for insight in insights:
try:
# Determine severity based on confidence and priority
confidence = insight.get('confidence', 0)
priority = insight.get('priority', 'medium')
if priority == 'urgent' or confidence >= 90:
severity = 'urgent'
elif priority == 'high' or confidence >= 70:
severity = 'high'
elif priority == 'medium' or confidence >= 50:
severity = 'medium'
else:
severity = 'low'
# Build event metadata
event_metadata = {
'insight_id': insight.get('id'),
'insight_type': insight.get('insight_type'),
'inventory_product_id': insight.get('metrics_json', {}).get('inventory_product_id'),
'ingredient_name': product_context.get('ingredient_name') if product_context else None,
'suggested_safety_stock': insight.get('metrics_json', {}).get('suggested_safety_stock'),
'current_safety_stock': insight.get('metrics_json', {}).get('current_safety_stock'),
'estimated_savings': insight.get('impact_value'),
'confidence': confidence,
'recommendation': insight.get('recommendation'),
'impact_type': insight.get('impact_type'),
'source_service': 'inventory',
'source_model': 'safety_stock_optimizer'
}
# Remove None values
event_metadata = {k: v for k, v in event_metadata.items() if v is not None}
# Publish recommendation event
await self.event_publisher.publish_recommendation(
event_type='ai_safety_stock_optimization',
tenant_id=tenant_id,
severity=severity,
data=event_metadata
)
logger.info(
"Published safety stock insight recommendation event",
tenant_id=tenant_id,
insight_id=insight.get('id'),
insight_type=insight.get('insight_type'),
severity=severity
)
except Exception as e:
logger.error(
"Failed to publish insight event",
tenant_id=tenant_id,
insight_id=insight.get('id'),
error=str(e),
exc_info=True
)
# Don't raise - we don't want to fail the whole workflow if event publishing fails
async def optimize_all_products(
self,
tenant_id: str,