Improve AI logic
This commit is contained in:
@@ -14,6 +14,7 @@ AuditLog = create_audit_log_model(Base)
|
||||
# Import all models to register them with the Base metadata
|
||||
from .tenants import Tenant, TenantMember, Subscription
|
||||
from .coupon import CouponModel, CouponRedemptionModel
|
||||
from .events import Event, EventTemplate
|
||||
|
||||
# List all models for easier access
|
||||
__all__ = [
|
||||
@@ -23,4 +24,6 @@ __all__ = [
|
||||
"AuditLog",
|
||||
"CouponModel",
|
||||
"CouponRedemptionModel",
|
||||
"Event",
|
||||
"EventTemplate",
|
||||
]
|
||||
|
||||
136
services/tenant/app/models/events.py
Normal file
136
services/tenant/app/models/events.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""
|
||||
Event Calendar Models
|
||||
Database models for tracking local events, promotions, and special occasions
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Text, Boolean, Float, Date
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from shared.database.base import Base
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
|
||||
|
||||
class Event(Base):
|
||||
"""
|
||||
Table to track events that affect bakery demand.
|
||||
|
||||
Events include:
|
||||
- Local events (festivals, markets, concerts)
|
||||
- Promotions and sales
|
||||
- Weather events (heat waves, storms)
|
||||
- School holidays and breaks
|
||||
- Special occasions
|
||||
"""
|
||||
__tablename__ = "events"
|
||||
|
||||
# Primary identification
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||
|
||||
# Event information
|
||||
event_name = Column(String(500), nullable=False)
|
||||
event_type = Column(String(100), nullable=False, index=True) # promotion, festival, holiday, weather, school_break, sport_event, etc.
|
||||
description = Column(Text, nullable=True)
|
||||
|
||||
# Date and time
|
||||
event_date = Column(Date, nullable=False, index=True)
|
||||
start_time = Column(DateTime(timezone=True), nullable=True)
|
||||
end_time = Column(DateTime(timezone=True), nullable=True)
|
||||
is_all_day = Column(Boolean, default=True)
|
||||
|
||||
# Impact estimation
|
||||
expected_impact = Column(String(50), nullable=True) # low, medium, high, very_high
|
||||
impact_multiplier = Column(Float, nullable=True) # Expected demand multiplier (e.g., 1.5 = 50% increase)
|
||||
affected_product_categories = Column(String(500), nullable=True) # Comma-separated categories
|
||||
|
||||
# Location
|
||||
location = Column(String(500), nullable=True)
|
||||
is_local = Column(Boolean, default=True) # True if event is near bakery
|
||||
|
||||
# Status
|
||||
is_confirmed = Column(Boolean, default=False)
|
||||
is_recurring = Column(Boolean, default=False)
|
||||
recurrence_pattern = Column(String(200), nullable=True) # e.g., "weekly:monday", "monthly:first_saturday"
|
||||
|
||||
# Actual impact (filled after event)
|
||||
actual_impact_multiplier = Column(Float, nullable=True)
|
||||
actual_sales_increase_percent = Column(Float, nullable=True)
|
||||
|
||||
# Metadata
|
||||
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(String(255), nullable=True)
|
||||
notes = Column(Text, nullable=True)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"tenant_id": str(self.tenant_id),
|
||||
"event_name": self.event_name,
|
||||
"event_type": self.event_type,
|
||||
"description": self.description,
|
||||
"event_date": self.event_date.isoformat() if self.event_date else None,
|
||||
"start_time": self.start_time.isoformat() if self.start_time else None,
|
||||
"end_time": self.end_time.isoformat() if self.end_time else None,
|
||||
"is_all_day": self.is_all_day,
|
||||
"expected_impact": self.expected_impact,
|
||||
"impact_multiplier": self.impact_multiplier,
|
||||
"affected_product_categories": self.affected_product_categories,
|
||||
"location": self.location,
|
||||
"is_local": self.is_local,
|
||||
"is_confirmed": self.is_confirmed,
|
||||
"is_recurring": self.is_recurring,
|
||||
"recurrence_pattern": self.recurrence_pattern,
|
||||
"actual_impact_multiplier": self.actual_impact_multiplier,
|
||||
"actual_sales_increase_percent": self.actual_sales_increase_percent,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
"created_by": self.created_by,
|
||||
"notes": self.notes
|
||||
}
|
||||
|
||||
|
||||
class EventTemplate(Base):
|
||||
"""
|
||||
Template for recurring events.
|
||||
Allows easy creation of events based on patterns.
|
||||
"""
|
||||
__tablename__ = "event_templates"
|
||||
|
||||
# Primary identification
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||
|
||||
# Template information
|
||||
template_name = Column(String(500), nullable=False)
|
||||
event_type = Column(String(100), nullable=False)
|
||||
description = Column(Text, nullable=True)
|
||||
|
||||
# Default values
|
||||
default_impact = Column(String(50), nullable=True)
|
||||
default_impact_multiplier = Column(Float, nullable=True)
|
||||
default_affected_categories = Column(String(500), nullable=True)
|
||||
|
||||
# Recurrence
|
||||
recurrence_pattern = Column(String(200), nullable=False) # e.g., "weekly:saturday", "monthly:last_sunday"
|
||||
is_active = Column(Boolean, default=True)
|
||||
|
||||
# Metadata
|
||||
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))
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"tenant_id": str(self.tenant_id),
|
||||
"template_name": self.template_name,
|
||||
"event_type": self.event_type,
|
||||
"description": self.description,
|
||||
"default_impact": self.default_impact,
|
||||
"default_impact_multiplier": self.default_impact_multiplier,
|
||||
"default_affected_categories": self.default_affected_categories,
|
||||
"recurrence_pattern": self.recurrence_pattern,
|
||||
"is_active": self.is_active,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
@@ -154,6 +154,32 @@ class TenantSettings(Base):
|
||||
"enable_supplier_score_optimization": True
|
||||
})
|
||||
|
||||
# ML Insights Settings (AI Insights Service)
|
||||
ml_insights_settings = Column(JSON, nullable=False, default=lambda: {
|
||||
# Inventory ML (Safety Stock Optimization)
|
||||
"inventory_lookback_days": 90,
|
||||
"inventory_min_history_days": 30,
|
||||
|
||||
# Production ML (Yield Prediction)
|
||||
"production_lookback_days": 90,
|
||||
"production_min_history_runs": 30,
|
||||
|
||||
# Procurement ML (Supplier Analysis & Price Forecasting)
|
||||
"supplier_analysis_lookback_days": 180,
|
||||
"supplier_analysis_min_orders": 10,
|
||||
"price_forecast_lookback_days": 180,
|
||||
"price_forecast_horizon_days": 30,
|
||||
|
||||
# Forecasting ML (Dynamic Rules)
|
||||
"rules_generation_lookback_days": 90,
|
||||
"rules_generation_min_samples": 10,
|
||||
|
||||
# Global ML Settings
|
||||
"enable_ml_insights": True,
|
||||
"ml_insights_auto_trigger": False,
|
||||
"ml_confidence_threshold": 0.80
|
||||
})
|
||||
|
||||
# Timestamps
|
||||
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
|
||||
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
|
||||
@@ -280,5 +306,20 @@ class TenantSettings(Base):
|
||||
"diversification_threshold": 1000,
|
||||
"max_single_percentage": 0.70,
|
||||
"enable_supplier_score_optimization": True
|
||||
},
|
||||
"ml_insights_settings": {
|
||||
"inventory_lookback_days": 90,
|
||||
"inventory_min_history_days": 30,
|
||||
"production_lookback_days": 90,
|
||||
"production_min_history_runs": 30,
|
||||
"supplier_analysis_lookback_days": 180,
|
||||
"supplier_analysis_min_orders": 10,
|
||||
"price_forecast_lookback_days": 180,
|
||||
"price_forecast_horizon_days": 30,
|
||||
"rules_generation_lookback_days": 90,
|
||||
"rules_generation_min_samples": 10,
|
||||
"enable_ml_insights": True,
|
||||
"ml_insights_auto_trigger": False,
|
||||
"ml_confidence_threshold": 0.80
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user