# ================================================================ # services/suppliers/app/models/performance.py # ================================================================ """ Supplier Performance Tracking and Alert Models for Suppliers Service Comprehensive supplier performance metrics, KPIs, and alert management """ from sqlalchemy import Column, String, DateTime, Float, Integer, Text, Index, Boolean, Numeric, ForeignKey, Enum as SQLEnum from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.orm import relationship import uuid import enum from datetime import datetime, timezone from typing import Dict, Any, Optional, List from decimal import Decimal from shared.database.base import Base class AlertSeverity(enum.Enum): """Alert severity levels""" CRITICAL = "critical" HIGH = "high" MEDIUM = "medium" LOW = "low" INFO = "info" class AlertType(enum.Enum): """Types of supplier alerts""" POOR_QUALITY = "poor_quality" LATE_DELIVERY = "late_delivery" PRICE_INCREASE = "price_increase" LOW_PERFORMANCE = "low_performance" CONTRACT_EXPIRY = "contract_expiry" COMPLIANCE_ISSUE = "compliance_issue" FINANCIAL_RISK = "financial_risk" COMMUNICATION_ISSUE = "communication_issue" CAPACITY_CONSTRAINT = "capacity_constraint" CERTIFICATION_EXPIRY = "certification_expiry" class AlertStatus(enum.Enum): """Alert processing status""" ACTIVE = "active" ACKNOWLEDGED = "acknowledged" IN_PROGRESS = "in_progress" RESOLVED = "resolved" DISMISSED = "dismissed" class PerformanceMetricType(enum.Enum): """Types of performance metrics""" DELIVERY_PERFORMANCE = "delivery_performance" QUALITY_SCORE = "quality_score" PRICE_COMPETITIVENESS = "price_competitiveness" COMMUNICATION_RATING = "communication_rating" ORDER_ACCURACY = "order_accuracy" RESPONSE_TIME = "response_time" COMPLIANCE_SCORE = "compliance_score" FINANCIAL_STABILITY = "financial_stability" class PerformancePeriod(enum.Enum): """Performance measurement periods""" DAILY = "daily" WEEKLY = "weekly" MONTHLY = "monthly" QUARTERLY = "quarterly" YEARLY = "yearly" class SupplierPerformanceMetric(Base): """Supplier performance metrics tracking""" __tablename__ = "supplier_performance_metrics" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) supplier_id = Column(UUID(as_uuid=True), ForeignKey('suppliers.id'), nullable=False, index=True) # Metric details metric_type = Column(SQLEnum(PerformanceMetricType), nullable=False, index=True) period = Column(SQLEnum(PerformancePeriod), nullable=False, index=True) period_start = Column(DateTime(timezone=True), nullable=False, index=True) period_end = Column(DateTime(timezone=True), nullable=False, index=True) # Performance values metric_value = Column(Float, nullable=False) # Main metric value (0-100 scale) target_value = Column(Float, nullable=True) # Target/benchmark value previous_value = Column(Float, nullable=True) # Previous period value for comparison # Supporting data total_orders = Column(Integer, nullable=False, default=0) total_deliveries = Column(Integer, nullable=False, default=0) on_time_deliveries = Column(Integer, nullable=False, default=0) late_deliveries = Column(Integer, nullable=False, default=0) quality_issues = Column(Integer, nullable=False, default=0) total_amount = Column(Numeric(12, 2), nullable=False, default=0.0) # Detailed metrics breakdown metrics_data = Column(JSONB, nullable=True) # Detailed breakdown of calculations # Performance trends trend_direction = Column(String(20), nullable=True) # improving, declining, stable trend_percentage = Column(Float, nullable=True) # % change from previous period # Contextual information notes = Column(Text, nullable=True) external_factors = Column(JSONB, nullable=True) # External factors affecting performance # Audit fields calculated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) calculated_by = Column(UUID(as_uuid=True), nullable=True) # System or user ID # Relationships supplier = relationship("Supplier") # Indexes __table_args__ = ( Index('ix_performance_metrics_tenant_supplier', 'tenant_id', 'supplier_id'), Index('ix_performance_metrics_type_period', 'metric_type', 'period'), Index('ix_performance_metrics_period_dates', 'period_start', 'period_end'), Index('ix_performance_metrics_value', 'metric_value'), ) class SupplierAlert(Base): """Supplier-related alerts and notifications""" __tablename__ = "supplier_alerts" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) supplier_id = Column(UUID(as_uuid=True), ForeignKey('suppliers.id'), nullable=False, index=True) # Alert classification alert_type = Column(SQLEnum(AlertType), nullable=False, index=True) severity = Column(SQLEnum(AlertSeverity), nullable=False, index=True) status = Column(SQLEnum(AlertStatus), nullable=False, default=AlertStatus.ACTIVE, index=True) # Alert content title = Column(String(255), nullable=False) message = Column(Text, nullable=False) description = Column(Text, nullable=True) # Alert triggers and context trigger_value = Column(Float, nullable=True) # The value that triggered the alert threshold_value = Column(Float, nullable=True) # The threshold that was exceeded metric_type = Column(SQLEnum(PerformanceMetricType), nullable=True, index=True) # Related entities purchase_order_id = Column(UUID(as_uuid=True), nullable=True, index=True) delivery_id = Column(UUID(as_uuid=True), nullable=True, index=True) performance_metric_id = Column(UUID(as_uuid=True), ForeignKey('supplier_performance_metrics.id'), nullable=True) # Alert lifecycle triggered_at = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc)) acknowledged_at = Column(DateTime(timezone=True), nullable=True) acknowledged_by = Column(UUID(as_uuid=True), nullable=True) resolved_at = Column(DateTime(timezone=True), nullable=True) resolved_by = Column(UUID(as_uuid=True), nullable=True) # Actions and resolution recommended_actions = Column(JSONB, nullable=True) # Suggested actions actions_taken = Column(JSONB, nullable=True) # Actions that were taken resolution_notes = Column(Text, nullable=True) # Auto-resolution auto_resolve = Column(Boolean, nullable=False, default=False) auto_resolve_condition = Column(JSONB, nullable=True) # Conditions for auto-resolution # Escalation escalated = Column(Boolean, nullable=False, default=False) escalated_at = Column(DateTime(timezone=True), nullable=True) escalated_to = Column(UUID(as_uuid=True), nullable=True) # User/role escalated to # Notification tracking notification_sent = Column(Boolean, nullable=False, default=False) notification_sent_at = Column(DateTime(timezone=True), nullable=True) notification_recipients = Column(JSONB, nullable=True) # List of recipients # Additional metadata priority_score = Column(Integer, nullable=False, default=50) # 1-100 priority scoring business_impact = Column(String(50), nullable=True) # high, medium, low impact tags = Column(JSONB, nullable=True) # Categorization tags # Audit fields created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) created_by = Column(UUID(as_uuid=True), nullable=True) # Relationships supplier = relationship("Supplier") performance_metric = relationship("SupplierPerformanceMetric") # Indexes __table_args__ = ( Index('ix_supplier_alerts_tenant_supplier', 'tenant_id', 'supplier_id'), Index('ix_supplier_alerts_type_severity', 'alert_type', 'severity'), Index('ix_supplier_alerts_status_triggered', 'status', 'triggered_at'), Index('ix_supplier_alerts_metric_type', 'metric_type'), Index('ix_supplier_alerts_priority', 'priority_score'), ) class SupplierScorecard(Base): """Comprehensive supplier scorecards for performance evaluation""" __tablename__ = "supplier_scorecards" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) supplier_id = Column(UUID(as_uuid=True), ForeignKey('suppliers.id'), nullable=False, index=True) # Scorecard details scorecard_name = Column(String(255), nullable=False) period = Column(SQLEnum(PerformancePeriod), nullable=False, index=True) period_start = Column(DateTime(timezone=True), nullable=False, index=True) period_end = Column(DateTime(timezone=True), nullable=False, index=True) # Overall performance scores overall_score = Column(Float, nullable=False) # Weighted overall score (0-100) quality_score = Column(Float, nullable=False) # Quality performance (0-100) delivery_score = Column(Float, nullable=False) # Delivery performance (0-100) cost_score = Column(Float, nullable=False) # Cost competitiveness (0-100) service_score = Column(Float, nullable=False) # Service quality (0-100) # Performance rankings overall_rank = Column(Integer, nullable=True) # Rank among all suppliers category_rank = Column(Integer, nullable=True) # Rank within supplier category total_suppliers_evaluated = Column(Integer, nullable=True) # Detailed performance breakdown on_time_delivery_rate = Column(Float, nullable=False) # % of on-time deliveries quality_rejection_rate = Column(Float, nullable=False) # % of quality rejections order_accuracy_rate = Column(Float, nullable=False) # % of accurate orders response_time_hours = Column(Float, nullable=False) # Average response time cost_variance_percentage = Column(Float, nullable=False) # Cost variance from budget # Business metrics total_orders_processed = Column(Integer, nullable=False, default=0) total_amount_processed = Column(Numeric(12, 2), nullable=False, default=0.0) average_order_value = Column(Numeric(10, 2), nullable=False, default=0.0) cost_savings_achieved = Column(Numeric(10, 2), nullable=False, default=0.0) # Performance trends score_trend = Column(String(20), nullable=True) # improving, declining, stable score_change_percentage = Column(Float, nullable=True) # % change from previous period # Recommendations and actions strengths = Column(JSONB, nullable=True) # List of strengths improvement_areas = Column(JSONB, nullable=True) # Areas for improvement recommended_actions = Column(JSONB, nullable=True) # Recommended actions # Scorecard status is_final = Column(Boolean, nullable=False, default=False) approved_by = Column(UUID(as_uuid=True), nullable=True) approved_at = Column(DateTime(timezone=True), nullable=True) # Additional information notes = Column(Text, nullable=True) attachments = Column(JSONB, nullable=True) # Supporting documents # Audit fields generated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) generated_by = Column(UUID(as_uuid=True), nullable=False) # Relationships supplier = relationship("Supplier") # Indexes __table_args__ = ( Index('ix_scorecards_tenant_supplier', 'tenant_id', 'supplier_id'), Index('ix_scorecards_period_dates', 'period_start', 'period_end'), Index('ix_scorecards_overall_score', 'overall_score'), Index('ix_scorecards_period', 'period'), Index('ix_scorecards_final', 'is_final'), ) class SupplierBenchmark(Base): """Supplier performance benchmarks and industry standards""" __tablename__ = "supplier_benchmarks" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Benchmark details benchmark_name = Column(String(255), nullable=False) benchmark_type = Column(String(50), nullable=False, index=True) # industry, internal, custom supplier_category = Column(String(100), nullable=True, index=True) # Target supplier category # Metric thresholds metric_type = Column(SQLEnum(PerformanceMetricType), nullable=False, index=True) excellent_threshold = Column(Float, nullable=False) # Excellent performance threshold good_threshold = Column(Float, nullable=False) # Good performance threshold acceptable_threshold = Column(Float, nullable=False) # Acceptable performance threshold poor_threshold = Column(Float, nullable=False) # Poor performance threshold # Benchmark context data_source = Column(String(255), nullable=True) # Source of benchmark data sample_size = Column(Integer, nullable=True) # Sample size for benchmark confidence_level = Column(Float, nullable=True) # Statistical confidence level # Validity and updates effective_date = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc)) expiry_date = Column(DateTime(timezone=True), nullable=True) is_active = Column(Boolean, nullable=False, default=True) # Additional information description = Column(Text, nullable=True) methodology = Column(Text, nullable=True) notes = Column(Text, nullable=True) # Audit fields created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) created_by = Column(UUID(as_uuid=True), nullable=False) # Indexes __table_args__ = ( Index('ix_benchmarks_tenant_type', 'tenant_id', 'benchmark_type'), Index('ix_benchmarks_metric_type', 'metric_type'), Index('ix_benchmarks_category', 'supplier_category'), Index('ix_benchmarks_active', 'is_active'), ) class AlertRule(Base): """Configurable alert rules for supplier performance monitoring""" __tablename__ = "alert_rules" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Rule identification rule_name = Column(String(255), nullable=False) rule_description = Column(Text, nullable=True) is_active = Column(Boolean, nullable=False, default=True) # Alert configuration alert_type = Column(SQLEnum(AlertType), nullable=False, index=True) severity = Column(SQLEnum(AlertSeverity), nullable=False) metric_type = Column(SQLEnum(PerformanceMetricType), nullable=True, index=True) # Trigger conditions trigger_condition = Column(String(50), nullable=False) # greater_than, less_than, equals, etc. threshold_value = Column(Float, nullable=False) consecutive_violations = Column(Integer, nullable=False, default=1) # How many consecutive violations before alert # Scope and filters supplier_categories = Column(JSONB, nullable=True) # Which supplier categories this applies to supplier_ids = Column(JSONB, nullable=True) # Specific suppliers (if applicable) exclude_suppliers = Column(JSONB, nullable=True) # Suppliers to exclude # Time constraints evaluation_period = Column(SQLEnum(PerformancePeriod), nullable=False) time_window_hours = Column(Integer, nullable=True) # Time window for evaluation business_hours_only = Column(Boolean, nullable=False, default=False) # Auto-resolution auto_resolve = Column(Boolean, nullable=False, default=False) auto_resolve_threshold = Column(Float, nullable=True) # Value at which alert auto-resolves auto_resolve_duration_hours = Column(Integer, nullable=True) # How long condition must be met # Notification settings notification_enabled = Column(Boolean, nullable=False, default=True) notification_recipients = Column(JSONB, nullable=True) # List of recipients escalation_minutes = Column(Integer, nullable=True) # Minutes before escalation escalation_recipients = Column(JSONB, nullable=True) # Escalation recipients # Action triggers recommended_actions = Column(JSONB, nullable=True) # Actions to recommend auto_actions = Column(JSONB, nullable=True) # Actions to automatically trigger # Rule metadata priority = Column(Integer, nullable=False, default=50) # Rule priority (1-100) tags = Column(JSONB, nullable=True) # Classification tags # Audit fields created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) created_by = Column(UUID(as_uuid=True), nullable=False) last_triggered = Column(DateTime(timezone=True), nullable=True) trigger_count = Column(Integer, nullable=False, default=0) # Indexes __table_args__ = ( Index('ix_alert_rules_tenant_active', 'tenant_id', 'is_active'), Index('ix_alert_rules_type_severity', 'alert_type', 'severity'), Index('ix_alert_rules_metric_type', 'metric_type'), Index('ix_alert_rules_priority', 'priority'), )