Files
bakery-ia/services/suppliers/app/services/dashboard_service.py
2025-10-24 13:05:04 +02:00

721 lines
31 KiB
Python

# ================================================================
# services/suppliers/app/services/dashboard_service.py
# ================================================================
"""
Supplier Dashboard and Analytics Service
Comprehensive supplier performance dashboards and business intelligence
"""
from datetime import datetime, timedelta, timezone
from typing import List, Optional, Dict, Any
from uuid import UUID
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_, or_, desc, asc, text
from decimal import Decimal
import structlog
from app.models.suppliers import (
Supplier, PurchaseOrder, Delivery, SupplierQualityReview,
SupplierStatus, SupplierType, PurchaseOrderStatus, DeliveryStatus
)
from app.models.performance import (
SupplierPerformanceMetric, SupplierScorecard, SupplierAlert,
PerformanceMetricType, PerformancePeriod, AlertSeverity, AlertStatus
)
from app.schemas.performance import (
PerformanceDashboardSummary, SupplierPerformanceInsights,
PerformanceAnalytics, BusinessModelInsights, AlertSummary
)
from app.core.config import settings
logger = structlog.get_logger()
class DashboardService:
"""Service for supplier performance dashboards and analytics"""
def __init__(self):
self.logger = logger.bind(service="dashboard_service")
async def get_performance_dashboard_summary(
self,
db: AsyncSession,
tenant_id: UUID,
date_from: Optional[datetime] = None,
date_to: Optional[datetime] = None
) -> PerformanceDashboardSummary:
"""Get comprehensive performance dashboard summary"""
try:
# Default date range - last 30 days
if not date_to:
date_to = datetime.now(timezone.utc)
if not date_from:
date_from = date_to - timedelta(days=30)
self.logger.info("Generating dashboard summary",
tenant_id=str(tenant_id),
date_from=date_from.isoformat(),
date_to=date_to.isoformat())
# Get supplier statistics
supplier_stats = await self._get_supplier_statistics(db, tenant_id)
# Get performance statistics
performance_stats = await self._get_performance_statistics(db, tenant_id, date_from, date_to)
# Get alert statistics
alert_stats = await self._get_alert_statistics(db, tenant_id, date_from, date_to)
# Get financial statistics
financial_stats = await self._get_financial_statistics(db, tenant_id, date_from, date_to)
# Get business model insights
business_model = await self._detect_business_model(db, tenant_id)
# Calculate trends
trends = await self._calculate_performance_trends(db, tenant_id, date_from, date_to)
return PerformanceDashboardSummary(
total_suppliers=supplier_stats['total_suppliers'],
active_suppliers=supplier_stats['active_suppliers'],
suppliers_above_threshold=performance_stats['above_threshold'],
suppliers_below_threshold=performance_stats['below_threshold'],
average_overall_score=performance_stats['avg_overall_score'],
average_delivery_rate=performance_stats['avg_delivery_rate'],
average_quality_rate=performance_stats['avg_quality_rate'],
total_active_alerts=alert_stats['total_active'],
critical_alerts=alert_stats['critical_alerts'],
high_priority_alerts=alert_stats['high_priority'],
recent_scorecards_generated=performance_stats['recent_scorecards'],
cost_savings_this_month=financial_stats['cost_savings'],
performance_trend=trends['performance_trend'],
delivery_trend=trends['delivery_trend'],
quality_trend=trends['quality_trend'],
detected_business_model=business_model['model'],
model_confidence=business_model['confidence'],
business_model_metrics=business_model['metrics']
)
except Exception as e:
self.logger.error("Error generating dashboard summary", error=str(e))
raise
async def get_supplier_performance_insights(
self,
db: AsyncSession,
tenant_id: UUID,
supplier_id: UUID,
days_back: int = 30
) -> SupplierPerformanceInsights:
"""Get detailed performance insights for a specific supplier"""
try:
date_to = datetime.now(timezone.utc)
date_from = date_to - timedelta(days=days_back)
# Get supplier info
supplier = await self._get_supplier_info(db, supplier_id, tenant_id)
# Get current performance metrics
current_metrics = await self._get_current_performance_metrics(db, supplier_id, tenant_id)
# Get previous period metrics for comparison
previous_metrics = await self._get_previous_performance_metrics(db, supplier_id, tenant_id, days_back)
# Get recent activity statistics
activity_stats = await self._get_supplier_activity_stats(db, supplier_id, tenant_id, date_from, date_to)
# Get alert summary
alert_summary = await self._get_supplier_alert_summary(db, supplier_id, tenant_id, date_from, date_to)
# Calculate performance categorization
performance_category = self._categorize_performance(current_metrics.get('overall_score', 0))
risk_level = self._assess_risk_level(current_metrics, alert_summary)
# Generate recommendations
recommendations = await self._generate_supplier_recommendations(
db, supplier_id, tenant_id, current_metrics, activity_stats, alert_summary
)
return SupplierPerformanceInsights(
supplier_id=supplier_id,
current_overall_score=current_metrics.get('overall_score', 0),
previous_score=previous_metrics.get('overall_score'),
score_change_percentage=self._calculate_change_percentage(
current_metrics.get('overall_score', 0),
previous_metrics.get('overall_score')
),
performance_rank=current_metrics.get('rank'),
delivery_performance=current_metrics.get('delivery_performance', 0),
quality_performance=current_metrics.get('quality_performance', 0),
cost_performance=current_metrics.get('cost_performance', 0),
service_performance=current_metrics.get('service_performance', 0),
orders_last_30_days=activity_stats['orders_count'],
average_delivery_time=activity_stats['avg_delivery_time'],
quality_issues_count=activity_stats['quality_issues'],
cost_variance=activity_stats['cost_variance'],
active_alerts=alert_summary['active_count'],
resolved_alerts_last_30_days=alert_summary['resolved_count'],
alert_trend=alert_summary['trend'],
performance_category=performance_category,
risk_level=risk_level,
top_strengths=recommendations['strengths'],
improvement_priorities=recommendations['improvements'],
recommended_actions=recommendations['actions']
)
except Exception as e:
self.logger.error("Error generating supplier insights",
supplier_id=str(supplier_id),
error=str(e))
raise
async def get_performance_analytics(
self,
db: AsyncSession,
tenant_id: UUID,
period_days: int = 90
) -> PerformanceAnalytics:
"""Get advanced performance analytics"""
try:
date_to = datetime.now(timezone.utc)
date_from = date_to - timedelta(days=period_days)
# Get performance distribution
performance_distribution = await self._get_performance_distribution(db, tenant_id, date_from, date_to)
# Get trend analysis
trends = await self._get_detailed_trends(db, tenant_id, date_from, date_to)
# Get comparative analysis
comparative_analysis = await self._get_comparative_analysis(db, tenant_id, date_from, date_to)
# Get risk analysis
risk_analysis = await self._get_risk_analysis(db, tenant_id, date_from, date_to)
# Get financial impact
financial_impact = await self._get_financial_impact(db, tenant_id, date_from, date_to)
return PerformanceAnalytics(
period_start=date_from,
period_end=date_to,
total_suppliers_analyzed=performance_distribution['total_suppliers'],
performance_distribution=performance_distribution['distribution'],
score_ranges=performance_distribution['score_ranges'],
overall_trend=trends['overall'],
delivery_trends=trends['delivery'],
quality_trends=trends['quality'],
cost_trends=trends['cost'],
top_performers=comparative_analysis['top_performers'],
underperformers=comparative_analysis['underperformers'],
most_improved=comparative_analysis['most_improved'],
biggest_declines=comparative_analysis['biggest_declines'],
high_risk_suppliers=risk_analysis['high_risk'],
contract_renewals_due=risk_analysis['contract_renewals'],
certification_expiries=risk_analysis['certification_expiries'],
total_procurement_value=financial_impact['total_value'],
cost_savings_achieved=financial_impact['cost_savings'],
cost_avoidance=financial_impact['cost_avoidance'],
financial_risk_exposure=financial_impact['risk_exposure']
)
except Exception as e:
self.logger.error("Error generating performance analytics", error=str(e))
raise
async def get_business_model_insights(
self,
db: AsyncSession,
tenant_id: UUID
) -> BusinessModelInsights:
"""Get business model detection and insights"""
try:
# Analyze supplier patterns
supplier_patterns = await self._analyze_supplier_patterns(db, tenant_id)
# Detect business model
business_model = await self._detect_business_model_detailed(db, tenant_id)
# Generate optimization recommendations
optimization = await self._generate_optimization_recommendations(db, tenant_id, business_model)
# Get benchmarking data
benchmarking = await self._get_benchmarking_data(db, tenant_id, business_model['model'])
return BusinessModelInsights(
detected_model=business_model['model'],
confidence_score=business_model['confidence'],
model_characteristics=business_model['characteristics'],
supplier_diversity_score=supplier_patterns['diversity_score'],
procurement_volume_patterns=supplier_patterns['volume_patterns'],
delivery_frequency_patterns=supplier_patterns['delivery_patterns'],
order_size_patterns=supplier_patterns['order_size_patterns'],
optimization_opportunities=optimization['opportunities'],
recommended_supplier_mix=optimization['supplier_mix'],
cost_optimization_potential=optimization['cost_potential'],
risk_mitigation_suggestions=optimization['risk_mitigation'],
industry_comparison=benchmarking['industry'],
peer_comparison=benchmarking.get('peer')
)
except Exception as e:
self.logger.error("Error generating business model insights", error=str(e))
raise
async def get_alert_summary(
self,
db: AsyncSession,
tenant_id: UUID,
date_from: Optional[datetime] = None,
date_to: Optional[datetime] = None
) -> List[AlertSummary]:
"""Get alert summary by type and severity"""
try:
if not date_to:
date_to = datetime.now(timezone.utc)
if not date_from:
date_from = date_to - timedelta(days=30)
query = select(
SupplierAlert.alert_type,
SupplierAlert.severity,
func.count(SupplierAlert.id).label('count'),
func.avg(
func.extract('epoch', SupplierAlert.resolved_at - SupplierAlert.triggered_at) / 3600
).label('avg_resolution_hours'),
func.max(
func.extract('epoch', func.current_timestamp() - SupplierAlert.triggered_at) / 3600
).label('oldest_age_hours')
).where(
and_(
SupplierAlert.tenant_id == tenant_id,
SupplierAlert.triggered_at >= date_from,
SupplierAlert.triggered_at <= date_to
)
).group_by(SupplierAlert.alert_type, SupplierAlert.severity)
result = await db.execute(query)
rows = result.all()
alert_summaries = []
for row in rows:
alert_summaries.append(AlertSummary(
alert_type=row.alert_type,
severity=row.severity,
count=row.count,
avg_resolution_time_hours=row.avg_resolution_hours,
oldest_alert_age_hours=row.oldest_age_hours
))
return alert_summaries
except Exception as e:
self.logger.error("Error getting alert summary", error=str(e))
raise
# === Private Helper Methods ===
async def _get_supplier_statistics(self, db: AsyncSession, tenant_id: UUID) -> Dict[str, int]:
"""Get basic supplier statistics"""
query = select(
func.count(Supplier.id).label('total_suppliers'),
func.count(Supplier.id.filter(Supplier.status == SupplierStatus.ACTIVE)).label('active_suppliers')
).where(Supplier.tenant_id == tenant_id)
result = await db.execute(query)
row = result.first()
return {
'total_suppliers': row.total_suppliers or 0,
'active_suppliers': row.active_suppliers or 0
}
async def _get_performance_statistics(
self,
db: AsyncSession,
tenant_id: UUID,
date_from: datetime,
date_to: datetime
) -> Dict[str, Any]:
"""Get performance statistics"""
# Get recent performance metrics
query = select(
func.avg(SupplierPerformanceMetric.metric_value).label('avg_score'),
func.count(
SupplierPerformanceMetric.id.filter(
SupplierPerformanceMetric.metric_value >= settings.GOOD_DELIVERY_RATE
)
).label('above_threshold'),
func.count(
SupplierPerformanceMetric.id.filter(
SupplierPerformanceMetric.metric_value < settings.GOOD_DELIVERY_RATE
)
).label('below_threshold')
).where(
and_(
SupplierPerformanceMetric.tenant_id == tenant_id,
SupplierPerformanceMetric.calculated_at >= date_from,
SupplierPerformanceMetric.calculated_at <= date_to,
SupplierPerformanceMetric.metric_type == PerformanceMetricType.DELIVERY_PERFORMANCE
)
)
result = await db.execute(query)
row = result.first()
# Get quality statistics
quality_query = select(
func.avg(SupplierPerformanceMetric.metric_value).label('avg_quality')
).where(
and_(
SupplierPerformanceMetric.tenant_id == tenant_id,
SupplierPerformanceMetric.calculated_at >= date_from,
SupplierPerformanceMetric.calculated_at <= date_to,
SupplierPerformanceMetric.metric_type == PerformanceMetricType.QUALITY_SCORE
)
)
quality_result = await db.execute(quality_query)
quality_row = quality_result.first()
# Get scorecard count
scorecard_query = select(func.count(SupplierScorecard.id)).where(
and_(
SupplierScorecard.tenant_id == tenant_id,
SupplierScorecard.generated_at >= date_from,
SupplierScorecard.generated_at <= date_to
)
)
scorecard_result = await db.execute(scorecard_query)
scorecard_count = scorecard_result.scalar() or 0
return {
'avg_overall_score': row.avg_score or 0,
'above_threshold': row.above_threshold or 0,
'below_threshold': row.below_threshold or 0,
'avg_delivery_rate': row.avg_score or 0,
'avg_quality_rate': quality_row.avg_quality or 0,
'recent_scorecards': scorecard_count
}
async def _get_alert_statistics(
self,
db: AsyncSession,
tenant_id: UUID,
date_from: datetime,
date_to: datetime
) -> Dict[str, int]:
"""Get alert statistics"""
query = select(
func.count(SupplierAlert.id.filter(SupplierAlert.status == AlertStatus.ACTIVE)).label('total_active'),
func.count(SupplierAlert.id.filter(SupplierAlert.severity == AlertSeverity.CRITICAL)).label('critical'),
func.count(SupplierAlert.id.filter(SupplierAlert.priority_score >= 70)).label('high_priority')
).where(
and_(
SupplierAlert.tenant_id == tenant_id,
SupplierAlert.triggered_at >= date_from,
SupplierAlert.triggered_at <= date_to
)
)
result = await db.execute(query)
row = result.first()
return {
'total_active': row.total_active or 0,
'critical_alerts': row.critical or 0,
'high_priority': row.high_priority or 0
}
async def _get_financial_statistics(
self,
db: AsyncSession,
tenant_id: UUID,
date_from: datetime,
date_to: datetime
) -> Dict[str, Decimal]:
"""Get financial statistics"""
# Calculate potential cost savings based on supplier performance
# Cost savings estimated from quality issues avoided, on-time deliveries, etc.
# Get purchase orders in period
query = select(
func.sum(PurchaseOrder.total_amount).label('total_spent')
).where(
and_(
PurchaseOrder.tenant_id == tenant_id,
PurchaseOrder.created_at >= date_from,
PurchaseOrder.created_at <= date_to,
PurchaseOrder.status.in_([
PurchaseOrderStatus.RECEIVED,
PurchaseOrderStatus.PARTIALLY_RECEIVED,
PurchaseOrderStatus.COMPLETED
])
)
)
result = await db.execute(query)
row = result.first()
total_spent = row.total_spent or Decimal('0')
# Estimate cost savings as 2-5% of total spent based on:
# - Better supplier selection
# - Reduced waste from quality issues
# - Better pricing through supplier comparison
estimated_savings_percentage = Decimal('0.03') # 3% conservative estimate
cost_savings = total_spent * estimated_savings_percentage
return {
'cost_savings': cost_savings
}
async def _detect_business_model(self, db: AsyncSession, tenant_id: UUID) -> Dict[str, Any]:
"""Detect business model based on supplier patterns"""
# Get supplier count by category
query = select(
func.count(Supplier.id).label('total_suppliers'),
func.count(Supplier.id.filter(Supplier.supplier_type == SupplierType.INGREDIENTS)).label('ingredient_suppliers')
).where(
and_(
Supplier.tenant_id == tenant_id,
Supplier.status == SupplierStatus.ACTIVE
)
)
result = await db.execute(query)
row = result.first()
total_suppliers = row.total_suppliers or 0
ingredient_suppliers = row.ingredient_suppliers or 0
# Simple business model detection logic
if total_suppliers >= settings.CENTRAL_BAKERY_THRESHOLD_SUPPLIERS:
model = "central_bakery"
confidence = 0.85
elif total_suppliers >= settings.INDIVIDUAL_BAKERY_THRESHOLD_SUPPLIERS:
model = "individual_bakery"
confidence = 0.75
else:
model = "small_bakery"
confidence = 0.60
return {
'model': model,
'confidence': confidence,
'metrics': {
'total_suppliers': total_suppliers,
'ingredient_suppliers': ingredient_suppliers,
'supplier_diversity': ingredient_suppliers / max(total_suppliers, 1)
}
}
async def _calculate_performance_trends(
self,
db: AsyncSession,
tenant_id: UUID,
date_from: datetime,
date_to: datetime
) -> Dict[str, str]:
"""Calculate performance trends based on historical data"""
# Calculate period length and compare with previous period
period_length = (date_to - date_from).days
previous_period_start = date_from - timedelta(days=period_length)
previous_period_end = date_from
# Get current period metrics
current_query = select(
func.avg(Supplier.delivery_rating).label('avg_delivery'),
func.avg(Supplier.quality_rating).label('avg_quality'),
func.count(PurchaseOrder.id).label('order_count')
).select_from(PurchaseOrder).join(
Supplier, PurchaseOrder.supplier_id == Supplier.id
).where(
and_(
PurchaseOrder.tenant_id == tenant_id,
PurchaseOrder.created_at >= date_from,
PurchaseOrder.created_at <= date_to
)
)
current_result = await db.execute(current_query)
current = current_result.first()
# Get previous period metrics
previous_query = select(
func.avg(Supplier.delivery_rating).label('avg_delivery'),
func.avg(Supplier.quality_rating).label('avg_quality'),
func.count(PurchaseOrder.id).label('order_count')
).select_from(PurchaseOrder).join(
Supplier, PurchaseOrder.supplier_id == Supplier.id
).where(
and_(
PurchaseOrder.tenant_id == tenant_id,
PurchaseOrder.created_at >= previous_period_start,
PurchaseOrder.created_at < previous_period_end
)
)
previous_result = await db.execute(previous_query)
previous = previous_result.first()
# Calculate trends
def calculate_trend(current_value, previous_value, threshold=0.05):
"""Calculate trend direction based on percentage change"""
if not current_value or not previous_value:
return 'stable'
change = (current_value - previous_value) / previous_value
if change > threshold:
return 'improving'
elif change < -threshold:
return 'declining'
return 'stable'
delivery_trend = calculate_trend(
current.avg_delivery if current else None,
previous.avg_delivery if previous else None
)
quality_trend = calculate_trend(
current.avg_quality if current else None,
previous.avg_quality if previous else None
)
# Overall performance based on both metrics
if delivery_trend == 'improving' and quality_trend == 'improving':
performance_trend = 'improving'
elif delivery_trend == 'declining' or quality_trend == 'declining':
performance_trend = 'declining'
else:
performance_trend = 'stable'
return {
'performance_trend': performance_trend,
'delivery_trend': delivery_trend,
'quality_trend': quality_trend
}
def _categorize_performance(self, score: float) -> str:
"""Categorize performance based on score"""
if score >= settings.EXCELLENT_DELIVERY_RATE:
return "excellent"
elif score >= settings.GOOD_DELIVERY_RATE:
return "good"
elif score >= settings.ACCEPTABLE_DELIVERY_RATE:
return "acceptable"
elif score >= settings.POOR_DELIVERY_RATE:
return "needs_improvement"
else:
return "poor"
def _assess_risk_level(self, metrics: Dict[str, Any], alerts: Dict[str, Any]) -> str:
"""Assess risk level based on metrics and alerts"""
if alerts.get('active_count', 0) > 3 or metrics.get('overall_score', 0) < 50:
return "critical"
elif alerts.get('active_count', 0) > 1 or metrics.get('overall_score', 0) < 70:
return "high"
elif alerts.get('active_count', 0) > 0 or metrics.get('overall_score', 0) < 85:
return "medium"
else:
return "low"
def _calculate_change_percentage(self, current: float, previous: Optional[float]) -> Optional[float]:
"""Calculate percentage change between current and previous values"""
if previous is None or previous == 0:
return None
return ((current - previous) / previous) * 100
# === Placeholder methods for complex analytics ===
# These methods return placeholder data and should be implemented with actual business logic
async def _get_supplier_info(self, db: AsyncSession, supplier_id: UUID, tenant_id: UUID) -> Dict[str, Any]:
stmt = select(Supplier).where(and_(Supplier.id == supplier_id, Supplier.tenant_id == tenant_id))
result = await db.execute(stmt)
supplier = result.scalar_one_or_none()
return {'name': supplier.name if supplier else 'Unknown Supplier'}
async def _get_current_performance_metrics(self, db: AsyncSession, supplier_id: UUID, tenant_id: UUID) -> Dict[str, Any]:
return {'overall_score': 75.0, 'delivery_performance': 80.0, 'quality_performance': 85.0, 'cost_performance': 70.0, 'service_performance': 75.0}
async def _get_previous_performance_metrics(self, db: AsyncSession, supplier_id: UUID, tenant_id: UUID, days_back: int) -> Dict[str, Any]:
return {'overall_score': 70.0}
async def _get_supplier_activity_stats(self, db: AsyncSession, supplier_id: UUID, tenant_id: UUID, date_from: datetime, date_to: datetime) -> Dict[str, Any]:
return {'orders_count': 15, 'avg_delivery_time': 3.2, 'quality_issues': 2, 'cost_variance': 5.5}
async def _get_supplier_alert_summary(self, db: AsyncSession, supplier_id: UUID, tenant_id: UUID, date_from: datetime, date_to: datetime) -> Dict[str, Any]:
return {'active_count': 1, 'resolved_count': 3, 'trend': 'improving'}
async def _generate_supplier_recommendations(self, db: AsyncSession, supplier_id: UUID, tenant_id: UUID, metrics: Dict[str, Any], activity: Dict[str, Any], alerts: Dict[str, Any]) -> Dict[str, Any]:
return {
'strengths': ['Consistent quality', 'Reliable delivery'],
'improvements': ['Cost optimization', 'Communication'],
'actions': [{'action': 'Negotiate better pricing', 'priority': 'high'}]
}
async def _get_performance_distribution(self, db: AsyncSession, tenant_id: UUID, date_from: datetime, date_to: datetime) -> Dict[str, Any]:
return {
'total_suppliers': 25,
'distribution': {'excellent': 5, 'good': 12, 'acceptable': 6, 'poor': 2},
'score_ranges': {'excellent': [95, 100, 97.5], 'good': [80, 94, 87.0]}
}
async def _get_detailed_trends(self, db: AsyncSession, tenant_id: UUID, date_from: datetime, date_to: datetime) -> Dict[str, Any]:
return {
'overall': {'month_over_month': 2.5},
'delivery': {'month_over_month': 1.8},
'quality': {'month_over_month': 3.2},
'cost': {'month_over_month': -1.5}
}
async def _get_comparative_analysis(self, db: AsyncSession, tenant_id: UUID, date_from: datetime, date_to: datetime) -> Dict[str, Any]:
return {
'top_performers': [],
'underperformers': [],
'most_improved': [],
'biggest_declines': []
}
async def _get_risk_analysis(self, db: AsyncSession, tenant_id: UUID, date_from: datetime, date_to: datetime) -> Dict[str, Any]:
return {
'high_risk': [],
'contract_renewals': [],
'certification_expiries': []
}
async def _get_financial_impact(self, db: AsyncSession, tenant_id: UUID, date_from: datetime, date_to: datetime) -> Dict[str, Any]:
return {
'total_value': Decimal('150000'),
'cost_savings': Decimal('5000'),
'cost_avoidance': Decimal('2000'),
'risk_exposure': Decimal('10000')
}
async def _analyze_supplier_patterns(self, db: AsyncSession, tenant_id: UUID) -> Dict[str, Any]:
return {
'diversity_score': 75.0,
'volume_patterns': {'peak_months': ['March', 'December']},
'delivery_patterns': {'frequency': 'weekly'},
'order_size_patterns': {'average_size': 'medium'}
}
async def _detect_business_model_detailed(self, db: AsyncSession, tenant_id: UUID) -> Dict[str, Any]:
return {
'model': 'individual_bakery',
'confidence': 0.85,
'characteristics': {'supplier_count': 15, 'order_frequency': 'weekly'}
}
async def _generate_optimization_recommendations(self, db: AsyncSession, tenant_id: UUID, business_model: Dict[str, Any]) -> Dict[str, Any]:
return {
'opportunities': [{'type': 'consolidation', 'potential_savings': '10%'}],
'supplier_mix': {'ingredients': '60%', 'packaging': '25%', 'services': '15%'},
'cost_potential': Decimal('5000'),
'risk_mitigation': ['Diversify supplier base', 'Implement backup suppliers']
}
async def _get_benchmarking_data(self, db: AsyncSession, tenant_id: UUID, business_model: str) -> Dict[str, Any]:
return {
'industry': {'delivery_rate': 88.5, 'quality_score': 91.2},
'peer': {'delivery_rate': 86.8, 'quality_score': 89.5}
}