285 lines
17 KiB
Python
285 lines
17 KiB
Python
"""add performance tracking tables
|
|
|
|
Revision ID: 002
|
|
Revises: 001
|
|
Create Date: 2024-12-19 12:00:00.000000
|
|
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.dialects import postgresql
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision = '002'
|
|
down_revision = '001'
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade():
|
|
# Create performance metric type enum
|
|
performance_metric_type = postgresql.ENUM(
|
|
'DELIVERY_PERFORMANCE', 'QUALITY_SCORE', 'PRICE_COMPETITIVENESS',
|
|
'COMMUNICATION_RATING', 'ORDER_ACCURACY', 'RESPONSE_TIME',
|
|
'COMPLIANCE_SCORE', 'FINANCIAL_STABILITY',
|
|
name='performancemetrictype'
|
|
)
|
|
performance_metric_type.create(op.get_bind())
|
|
|
|
# Create performance period enum
|
|
performance_period = postgresql.ENUM(
|
|
'DAILY', 'WEEKLY', 'MONTHLY', 'QUARTERLY', 'YEARLY',
|
|
name='performanceperiod'
|
|
)
|
|
performance_period.create(op.get_bind())
|
|
|
|
# Create alert severity enum
|
|
alert_severity = postgresql.ENUM(
|
|
'CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO',
|
|
name='alertseverity'
|
|
)
|
|
alert_severity.create(op.get_bind())
|
|
|
|
# Create alert type enum
|
|
alert_type = postgresql.ENUM(
|
|
'POOR_QUALITY', 'LATE_DELIVERY', 'PRICE_INCREASE', 'LOW_PERFORMANCE',
|
|
'CONTRACT_EXPIRY', 'COMPLIANCE_ISSUE', 'FINANCIAL_RISK',
|
|
'COMMUNICATION_ISSUE', 'CAPACITY_CONSTRAINT', 'CERTIFICATION_EXPIRY',
|
|
name='alerttype'
|
|
)
|
|
alert_type.create(op.get_bind())
|
|
|
|
# Create alert status enum
|
|
alert_status = postgresql.ENUM(
|
|
'ACTIVE', 'ACKNOWLEDGED', 'IN_PROGRESS', 'RESOLVED', 'DISMISSED',
|
|
name='alertstatus'
|
|
)
|
|
alert_status.create(op.get_bind())
|
|
|
|
# Create supplier performance metrics table
|
|
op.create_table('supplier_performance_metrics',
|
|
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('tenant_id', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('supplier_id', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('metric_type', performance_metric_type, nullable=False),
|
|
sa.Column('period', performance_period, nullable=False),
|
|
sa.Column('period_start', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('period_end', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('metric_value', sa.Float(), nullable=False),
|
|
sa.Column('target_value', sa.Float(), nullable=True),
|
|
sa.Column('previous_value', sa.Float(), nullable=True),
|
|
sa.Column('total_orders', sa.Integer(), nullable=False, default=0),
|
|
sa.Column('total_deliveries', sa.Integer(), nullable=False, default=0),
|
|
sa.Column('on_time_deliveries', sa.Integer(), nullable=False, default=0),
|
|
sa.Column('late_deliveries', sa.Integer(), nullable=False, default=0),
|
|
sa.Column('quality_issues', sa.Integer(), nullable=False, default=0),
|
|
sa.Column('total_amount', sa.Numeric(precision=12, scale=2), nullable=False, default=0.0),
|
|
sa.Column('metrics_data', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('trend_direction', sa.String(length=20), nullable=True),
|
|
sa.Column('trend_percentage', sa.Float(), nullable=True),
|
|
sa.Column('notes', sa.Text(), nullable=True),
|
|
sa.Column('external_factors', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('calculated_at', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('calculated_by', postgresql.UUID(as_uuid=True), nullable=True),
|
|
sa.ForeignKeyConstraint(['supplier_id'], ['suppliers.id'], ),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
|
|
# Create indexes for performance metrics
|
|
op.create_index('ix_performance_metrics_tenant_supplier', 'supplier_performance_metrics', ['tenant_id', 'supplier_id'])
|
|
op.create_index('ix_performance_metrics_type_period', 'supplier_performance_metrics', ['metric_type', 'period'])
|
|
op.create_index('ix_performance_metrics_period_dates', 'supplier_performance_metrics', ['period_start', 'period_end'])
|
|
op.create_index('ix_performance_metrics_value', 'supplier_performance_metrics', ['metric_value'])
|
|
|
|
# Create supplier alerts table
|
|
op.create_table('supplier_alerts',
|
|
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('tenant_id', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('supplier_id', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('alert_type', alert_type, nullable=False),
|
|
sa.Column('severity', alert_severity, nullable=False),
|
|
sa.Column('status', alert_status, nullable=False, default='ACTIVE'),
|
|
sa.Column('title', sa.String(length=255), nullable=False),
|
|
sa.Column('message', sa.Text(), nullable=False),
|
|
sa.Column('description', sa.Text(), nullable=True),
|
|
sa.Column('trigger_value', sa.Float(), nullable=True),
|
|
sa.Column('threshold_value', sa.Float(), nullable=True),
|
|
sa.Column('metric_type', performance_metric_type, nullable=True),
|
|
sa.Column('purchase_order_id', postgresql.UUID(as_uuid=True), nullable=True),
|
|
sa.Column('delivery_id', postgresql.UUID(as_uuid=True), nullable=True),
|
|
sa.Column('performance_metric_id', postgresql.UUID(as_uuid=True), nullable=True),
|
|
sa.Column('triggered_at', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('acknowledged_at', sa.DateTime(timezone=True), nullable=True),
|
|
sa.Column('acknowledged_by', postgresql.UUID(as_uuid=True), nullable=True),
|
|
sa.Column('resolved_at', sa.DateTime(timezone=True), nullable=True),
|
|
sa.Column('resolved_by', postgresql.UUID(as_uuid=True), nullable=True),
|
|
sa.Column('recommended_actions', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('actions_taken', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('resolution_notes', sa.Text(), nullable=True),
|
|
sa.Column('auto_resolve', sa.Boolean(), nullable=False, default=False),
|
|
sa.Column('auto_resolve_condition', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('escalated', sa.Boolean(), nullable=False, default=False),
|
|
sa.Column('escalated_at', sa.DateTime(timezone=True), nullable=True),
|
|
sa.Column('escalated_to', postgresql.UUID(as_uuid=True), nullable=True),
|
|
sa.Column('notification_sent', sa.Boolean(), nullable=False, default=False),
|
|
sa.Column('notification_sent_at', sa.DateTime(timezone=True), nullable=True),
|
|
sa.Column('notification_recipients', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('priority_score', sa.Integer(), nullable=False, default=50),
|
|
sa.Column('business_impact', sa.String(length=50), nullable=True),
|
|
sa.Column('tags', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('created_by', postgresql.UUID(as_uuid=True), nullable=True),
|
|
sa.ForeignKeyConstraint(['performance_metric_id'], ['supplier_performance_metrics.id'], ),
|
|
sa.ForeignKeyConstraint(['supplier_id'], ['suppliers.id'], ),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
|
|
# Create indexes for alerts
|
|
op.create_index('ix_supplier_alerts_tenant_supplier', 'supplier_alerts', ['tenant_id', 'supplier_id'])
|
|
op.create_index('ix_supplier_alerts_type_severity', 'supplier_alerts', ['alert_type', 'severity'])
|
|
op.create_index('ix_supplier_alerts_status_triggered', 'supplier_alerts', ['status', 'triggered_at'])
|
|
op.create_index('ix_supplier_alerts_metric_type', 'supplier_alerts', ['metric_type'])
|
|
op.create_index('ix_supplier_alerts_priority', 'supplier_alerts', ['priority_score'])
|
|
|
|
# Create supplier scorecards table
|
|
op.create_table('supplier_scorecards',
|
|
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('tenant_id', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('supplier_id', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('scorecard_name', sa.String(length=255), nullable=False),
|
|
sa.Column('period', performance_period, nullable=False),
|
|
sa.Column('period_start', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('period_end', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('overall_score', sa.Float(), nullable=False),
|
|
sa.Column('quality_score', sa.Float(), nullable=False),
|
|
sa.Column('delivery_score', sa.Float(), nullable=False),
|
|
sa.Column('cost_score', sa.Float(), nullable=False),
|
|
sa.Column('service_score', sa.Float(), nullable=False),
|
|
sa.Column('overall_rank', sa.Integer(), nullable=True),
|
|
sa.Column('category_rank', sa.Integer(), nullable=True),
|
|
sa.Column('total_suppliers_evaluated', sa.Integer(), nullable=True),
|
|
sa.Column('on_time_delivery_rate', sa.Float(), nullable=False),
|
|
sa.Column('quality_rejection_rate', sa.Float(), nullable=False),
|
|
sa.Column('order_accuracy_rate', sa.Float(), nullable=False),
|
|
sa.Column('response_time_hours', sa.Float(), nullable=False),
|
|
sa.Column('cost_variance_percentage', sa.Float(), nullable=False),
|
|
sa.Column('total_orders_processed', sa.Integer(), nullable=False, default=0),
|
|
sa.Column('total_amount_processed', sa.Numeric(precision=12, scale=2), nullable=False, default=0.0),
|
|
sa.Column('average_order_value', sa.Numeric(precision=10, scale=2), nullable=False, default=0.0),
|
|
sa.Column('cost_savings_achieved', sa.Numeric(precision=10, scale=2), nullable=False, default=0.0),
|
|
sa.Column('score_trend', sa.String(length=20), nullable=True),
|
|
sa.Column('score_change_percentage', sa.Float(), nullable=True),
|
|
sa.Column('strengths', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('improvement_areas', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('recommended_actions', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('is_final', sa.Boolean(), nullable=False, default=False),
|
|
sa.Column('approved_by', postgresql.UUID(as_uuid=True), nullable=True),
|
|
sa.Column('approved_at', sa.DateTime(timezone=True), nullable=True),
|
|
sa.Column('notes', sa.Text(), nullable=True),
|
|
sa.Column('attachments', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('generated_at', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('generated_by', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.ForeignKeyConstraint(['supplier_id'], ['suppliers.id'], ),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
|
|
# Create indexes for scorecards
|
|
op.create_index('ix_scorecards_tenant_supplier', 'supplier_scorecards', ['tenant_id', 'supplier_id'])
|
|
op.create_index('ix_scorecards_period_dates', 'supplier_scorecards', ['period_start', 'period_end'])
|
|
op.create_index('ix_scorecards_overall_score', 'supplier_scorecards', ['overall_score'])
|
|
op.create_index('ix_scorecards_period', 'supplier_scorecards', ['period'])
|
|
op.create_index('ix_scorecards_final', 'supplier_scorecards', ['is_final'])
|
|
|
|
# Create supplier benchmarks table
|
|
op.create_table('supplier_benchmarks',
|
|
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('tenant_id', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('benchmark_name', sa.String(length=255), nullable=False),
|
|
sa.Column('benchmark_type', sa.String(length=50), nullable=False),
|
|
sa.Column('supplier_category', sa.String(length=100), nullable=True),
|
|
sa.Column('metric_type', performance_metric_type, nullable=False),
|
|
sa.Column('excellent_threshold', sa.Float(), nullable=False),
|
|
sa.Column('good_threshold', sa.Float(), nullable=False),
|
|
sa.Column('acceptable_threshold', sa.Float(), nullable=False),
|
|
sa.Column('poor_threshold', sa.Float(), nullable=False),
|
|
sa.Column('data_source', sa.String(length=255), nullable=True),
|
|
sa.Column('sample_size', sa.Integer(), nullable=True),
|
|
sa.Column('confidence_level', sa.Float(), nullable=True),
|
|
sa.Column('effective_date', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('expiry_date', sa.DateTime(timezone=True), nullable=True),
|
|
sa.Column('is_active', sa.Boolean(), nullable=False, default=True),
|
|
sa.Column('description', sa.Text(), nullable=True),
|
|
sa.Column('methodology', sa.Text(), nullable=True),
|
|
sa.Column('notes', sa.Text(), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('created_by', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
|
|
# Create indexes for benchmarks
|
|
op.create_index('ix_benchmarks_tenant_type', 'supplier_benchmarks', ['tenant_id', 'benchmark_type'])
|
|
op.create_index('ix_benchmarks_metric_type', 'supplier_benchmarks', ['metric_type'])
|
|
op.create_index('ix_benchmarks_category', 'supplier_benchmarks', ['supplier_category'])
|
|
op.create_index('ix_benchmarks_active', 'supplier_benchmarks', ['is_active'])
|
|
|
|
# Create alert rules table
|
|
op.create_table('alert_rules',
|
|
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('tenant_id', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('rule_name', sa.String(length=255), nullable=False),
|
|
sa.Column('rule_description', sa.Text(), nullable=True),
|
|
sa.Column('is_active', sa.Boolean(), nullable=False, default=True),
|
|
sa.Column('alert_type', alert_type, nullable=False),
|
|
sa.Column('severity', alert_severity, nullable=False),
|
|
sa.Column('metric_type', performance_metric_type, nullable=True),
|
|
sa.Column('trigger_condition', sa.String(length=50), nullable=False),
|
|
sa.Column('threshold_value', sa.Float(), nullable=False),
|
|
sa.Column('consecutive_violations', sa.Integer(), nullable=False, default=1),
|
|
sa.Column('supplier_categories', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('supplier_ids', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('exclude_suppliers', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('evaluation_period', performance_period, nullable=False),
|
|
sa.Column('time_window_hours', sa.Integer(), nullable=True),
|
|
sa.Column('business_hours_only', sa.Boolean(), nullable=False, default=False),
|
|
sa.Column('auto_resolve', sa.Boolean(), nullable=False, default=False),
|
|
sa.Column('auto_resolve_threshold', sa.Float(), nullable=True),
|
|
sa.Column('auto_resolve_duration_hours', sa.Integer(), nullable=True),
|
|
sa.Column('notification_enabled', sa.Boolean(), nullable=False, default=True),
|
|
sa.Column('notification_recipients', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('escalation_minutes', sa.Integer(), nullable=True),
|
|
sa.Column('escalation_recipients', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('recommended_actions', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('auto_actions', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('priority', sa.Integer(), nullable=False, default=50),
|
|
sa.Column('tags', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column('created_by', postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column('last_triggered', sa.DateTime(timezone=True), nullable=True),
|
|
sa.Column('trigger_count', sa.Integer(), nullable=False, default=0),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
|
|
# Create indexes for alert rules
|
|
op.create_index('ix_alert_rules_tenant_active', 'alert_rules', ['tenant_id', 'is_active'])
|
|
op.create_index('ix_alert_rules_type_severity', 'alert_rules', ['alert_type', 'severity'])
|
|
op.create_index('ix_alert_rules_metric_type', 'alert_rules', ['metric_type'])
|
|
op.create_index('ix_alert_rules_priority', 'alert_rules', ['priority'])
|
|
|
|
|
|
def downgrade():
|
|
# Drop all tables and indexes
|
|
op.drop_table('alert_rules')
|
|
op.drop_table('supplier_benchmarks')
|
|
op.drop_table('supplier_scorecards')
|
|
op.drop_table('supplier_alerts')
|
|
op.drop_table('supplier_performance_metrics')
|
|
|
|
# Drop enums
|
|
op.execute('DROP TYPE IF EXISTS alertstatus')
|
|
op.execute('DROP TYPE IF EXISTS alerttype')
|
|
op.execute('DROP TYPE IF EXISTS alertseverity')
|
|
op.execute('DROP TYPE IF EXISTS performanceperiod')
|
|
op.execute('DROP TYPE IF EXISTS performancemetrictype') |