392 lines
18 KiB
Python
392 lines
18 KiB
Python
|
|
# ================================================================
|
||
|
|
# 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'),
|
||
|
|
)
|