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,

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.supplier_performance_predictor import SupplierPerformancePredictor
@@ -28,16 +29,19 @@ class SupplierInsightsOrchestrator:
1. Analyze supplier performance from historical orders
2. Generate insights for procurement risk management
3. Post insights to AI Insights Service
4. Provide supplier comparison and recommendations
5. Track supplier reliability scores
4. Publish recommendation events to RabbitMQ
5. Provide supplier comparison and recommendations
6. Track supplier reliability scores
"""
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.predictor = SupplierPerformancePredictor()
self.ai_insights_client = AIInsightsClient(ai_insights_base_url)
self.event_publisher = event_publisher
async def analyze_and_post_supplier_insights(
self,
@@ -105,7 +109,17 @@ class SupplierInsightsOrchestrator:
post_results = {'total': 0, 'successful': 0, 'failed': 0}
logger.info("No insights to post for supplier", supplier_id=supplier_id)
# Step 4: Return comprehensive results
# Step 4: Publish insight events to RabbitMQ
created_insights = post_results.get('created_insights', [])
if created_insights:
supplier_context = {'supplier_id': supplier_id}
await self._publish_insight_events(
tenant_id=tenant_id,
insights=created_insights,
supplier_context=supplier_context
)
# Step 5: Return comprehensive results
return {
'tenant_id': tenant_id,
'supplier_id': supplier_id,
@@ -159,6 +173,71 @@ class SupplierInsightsOrchestrator:
return enriched
async def _publish_insight_events(self, tenant_id, insights, supplier_context=None):
"""
Publish insight events to RabbitMQ for alert processing.
Args:
tenant_id: Tenant identifier
insights: List of created insights
supplier_context: Additional context about the supplier
"""
if not self.event_publisher:
logger.warning("No event publisher available for supplier 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'),
'supplier_id': supplier_context.get('supplier_id') if supplier_context else None,
'timestamp': insight.get('detected_at', datetime.utcnow().isoformat()),
'source_service': 'procurement',
'source_model': 'supplier_performance_predictor'
}
try:
await self.event_publisher.publish_recommendation(
event_type='ai_supplier_recommendation',
tenant_id=tenant_id,
severity=severity,
data=event_data
)
logger.info(
"Published supplier insight event",
tenant_id=tenant_id,
insight_id=insight.get('id'),
severity=severity
)
except Exception as e:
logger.error(
"Failed to publish supplier insight event",
tenant_id=tenant_id,
insight_id=insight.get('id'),
error=str(e)
)
async def analyze_all_suppliers(
self,
tenant_id: str,