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.price_forecaster import PriceForecaster
@@ -33,10 +34,12 @@ class PriceInsightsOrchestrator:
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.forecaster = PriceForecaster()
self.ai_insights_client = AIInsightsClient(ai_insights_base_url)
self.event_publisher = event_publisher
async def forecast_and_post_insights(
self,
@@ -107,7 +110,17 @@ class PriceInsightsOrchestrator:
post_results = {'total': 0, 'successful': 0, 'failed': 0}
logger.info("No insights to post for ingredient", ingredient_id=ingredient_id)
# Step 4: Return comprehensive results
# Step 4: Publish insight events to RabbitMQ
created_insights = post_results.get('created_insights', [])
if created_insights:
ingredient_context = {'ingredient_id': ingredient_id}
await self._publish_insight_events(
tenant_id=tenant_id,
insights=created_insights,
ingredient_context=ingredient_context
)
# Step 5: Return comprehensive results
return {
'tenant_id': tenant_id,
'ingredient_id': ingredient_id,
@@ -261,6 +274,71 @@ class PriceInsightsOrchestrator:
'bulk_opportunity_count': bulk_opportunity_count
}
async def _publish_insight_events(self, tenant_id, insights, ingredient_context=None):
"""
Publish insight events to RabbitMQ for alert processing.
Args:
tenant_id: Tenant identifier
insights: List of created insights
ingredient_context: Additional context about the ingredient
"""
if not self.event_publisher:
logger.warning("No event publisher available for price insights")
return
for insight in insights:
# Determine severity based on confidence and priority
confidence = insight.get('confidence', 0)
priority = insight.get('priority', 'medium')
# Map priority to severity, with confidence as tiebreaker
if priority == 'critical' or (priority == 'high' and confidence >= 70):
severity = 'high'
elif priority == 'high' or (priority == 'medium' and confidence >= 80):
severity = 'medium'
else:
severity = 'low'
# Prepare the event data
event_data = {
'insight_id': insight.get('id'),
'type': insight.get('type'),
'title': insight.get('title'),
'description': insight.get('description'),
'category': insight.get('category'),
'priority': insight.get('priority'),
'confidence': confidence,
'recommendation': insight.get('recommendation_actions', []),
'impact_type': insight.get('impact_type'),
'impact_value': insight.get('impact_value'),
'ingredient_id': ingredient_context.get('ingredient_id') if ingredient_context else None,
'timestamp': insight.get('detected_at', datetime.utcnow().isoformat()),
'source_service': 'procurement',
'source_model': 'price_forecaster'
}
try:
await self.event_publisher.publish_recommendation(
event_type='ai_price_forecast',
tenant_id=tenant_id,
severity=severity,
data=event_data
)
logger.info(
"Published price insight event",
tenant_id=tenant_id,
insight_id=insight.get('id'),
severity=severity
)
except Exception as e:
logger.error(
"Failed to publish price insight event",
tenant_id=tenant_id,
insight_id=insight.get('id'),
error=str(e)
)
def _generate_portfolio_summary_insight(
self,
tenant_id: str,