Files
bakery-ia/services/suppliers/app/models/performance.py

392 lines
18 KiB
Python
Raw Normal View History

2025-08-21 20:28:14 +02:00
# ================================================================
# 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'),
)