""" Demo AI Models Seed Script Creates fake AI models for demo tenants to populate the models list without having actual trained model files. This script uses hardcoded tenant and product IDs to avoid cross-database dependencies. """ import asyncio import sys import os from uuid import UUID from datetime import datetime, timezone, timedelta from decimal import Decimal # Add project root to path sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) from sqlalchemy import select from shared.database.base import create_database_manager import structlog # Import models - these paths work both locally and in container try: # Container environment (training-service image) from app.models.training import TrainedModel except ImportError: # Local environment from services.training.app.models.training import TrainedModel logger = structlog.get_logger() # ============================================================================ # HARDCODED DEMO DATA (from seed scripts) # ============================================================================ # Demo Tenant IDs (from seed_demo_tenants.py) DEMO_TENANT_PROFESSIONAL = UUID("a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6") # Panadería Artesana Madrid DEMO_TENANT_ENTERPRISE_CHAIN = UUID("c3d4e5f6-a7b8-49c0-d1e2-f3a4b5c6d7e8") # Enterprise parent (Obrador) DEMO_PRODUCTS = { DEMO_TENANT_PROFESSIONAL: [ {"id": UUID("20000000-0000-0000-0000-000000000001"), "name": "Baguette Tradicional"}, {"id": UUID("20000000-0000-0000-0000-000000000002"), "name": "Croissant de Mantequilla"}, {"id": UUID("20000000-0000-0000-0000-000000000003"), "name": "Pan de Pueblo"}, {"id": UUID("20000000-0000-0000-0000-000000000004"), "name": "Napolitana de Chocolate"}, ], DEMO_TENANT_ENTERPRISE_CHAIN: [ # Same products as professional but for enterprise parent (Obrador) {"id": UUID("20000000-0000-0000-0000-000000000001"), "name": "Baguette Tradicional"}, {"id": UUID("20000000-0000-0000-0000-000000000002"), "name": "Croissant de Mantequilla"}, {"id": UUID("20000000-0000-0000-0000-000000000003"), "name": "Pan de Pueblo"}, {"id": UUID("20000000-0000-0000-0000-000000000004"), "name": "Napolitana de Chocolate"}, ] } class DemoAIModelSeeder: """Seed fake AI models for demo tenants""" def __init__(self): self.training_db_url = os.getenv("TRAINING_DATABASE_URL") or os.getenv("DATABASE_URL") if not self.training_db_url: raise ValueError("Missing TRAINING_DATABASE_URL or DATABASE_URL") # Convert to async URL if needed if self.training_db_url.startswith("postgresql://"): self.training_db_url = self.training_db_url.replace( "postgresql://", "postgresql+asyncpg://", 1 ) self.training_db = create_database_manager(self.training_db_url, "demo-ai-seed") async def create_fake_model(self, session, tenant_id: UUID, product_info: dict): """Create a fake AI model entry for a product""" now = datetime.now(timezone.utc) training_start = now - timedelta(days=90) training_end = now - timedelta(days=7) fake_model = TrainedModel( tenant_id=tenant_id, inventory_product_id=product_info["id"], model_type="prophet_optimized", model_version="1.0-demo", job_id=f"demo-job-{tenant_id}-{product_info['id']}", # Fake file paths (files don't actually exist) model_path=f"/fake/models/{tenant_id}/{product_info['id']}/model.pkl", metadata_path=f"/fake/models/{tenant_id}/{product_info['id']}/metadata.json", # Fake but realistic metrics mape=Decimal("12.5"), # Mean Absolute Percentage Error mae=Decimal("2.3"), # Mean Absolute Error rmse=Decimal("3.1"), # Root Mean Squared Error r2_score=Decimal("0.85"), # R-squared training_samples=60, # 60 days of training data # Fake hyperparameters hyperparameters={ "changepoint_prior_scale": 0.05, "seasonality_prior_scale": 10.0, "holidays_prior_scale": 10.0, "seasonality_mode": "multiplicative" }, # Features used features_used=["weekday", "month", "is_holiday", "temperature", "precipitation"], # Normalization params (fake) normalization_params={ "temperature": {"mean": 15.0, "std": 5.0}, "precipitation": {"mean": 2.0, "std": 1.5} }, # Model status is_active=True, is_production=False, # Demo models are not production-ready # Training data info training_start_date=training_start, training_end_date=training_end, data_quality_score=Decimal("0.75"), # Good but not excellent # Metadata notes=f"Demo model for {product_info['name']} - No actual trained file exists. For demonstration purposes only.", created_by="demo-seed-script", created_at=now, updated_at=now, last_used_at=None ) session.add(fake_model) return fake_model async def seed_models_for_tenant(self, tenant_id: UUID, tenant_name: str, products: list): """Create fake AI models for a demo tenant""" logger.info( "Creating fake AI models for demo tenant", tenant_id=str(tenant_id), tenant_name=tenant_name, product_count=len(products) ) try: async with self.training_db.get_session() as session: models_created = 0 for product in products: # Check if model already exists result = await session.execute( select(TrainedModel).where( TrainedModel.tenant_id == tenant_id, TrainedModel.inventory_product_id == product["id"] ) ) existing_model = result.scalars().first() if existing_model: logger.info( "Model already exists, skipping", tenant_id=str(tenant_id), product_name=product["name"], product_id=str(product["id"]) ) continue # Create fake model model = await self.create_fake_model(session, tenant_id, product) models_created += 1 logger.info( "Created fake AI model", tenant_id=str(tenant_id), product_name=product["name"], product_id=str(product["id"]), model_id=str(model.id) ) await session.commit() logger.info( "✅ Successfully created fake AI models for tenant", tenant_id=str(tenant_id), tenant_name=tenant_name, models_created=models_created ) return models_created except Exception as e: logger.error( "❌ Error creating fake AI models for tenant", tenant_id=str(tenant_id), tenant_name=tenant_name, error=str(e), exc_info=True ) raise async def seed_all_demo_models(self): """Seed fake AI models for all demo tenants""" logger.info("=" * 80) logger.info("🤖 Starting Demo AI Models Seeding") logger.info("=" * 80) total_models_created = 0 try: # Professional Bakery (single location) professional_count = await self.seed_models_for_tenant( tenant_id=DEMO_TENANT_PROFESSIONAL, tenant_name="Panadería Artesana Madrid (Professional)", products=DEMO_PRODUCTS[DEMO_TENANT_PROFESSIONAL] ) total_models_created += professional_count # Enterprise Parent (central production - Obrador) enterprise_count = await self.seed_models_for_tenant( tenant_id=DEMO_TENANT_ENTERPRISE_CHAIN, tenant_name="Panadería Central - Obrador Madrid (Enterprise Parent)", products=DEMO_PRODUCTS[DEMO_TENANT_ENTERPRISE_CHAIN] ) total_models_created += enterprise_count logger.info("=" * 80) logger.info( "✅ Demo AI Models Seeding Completed", total_models_created=total_models_created, tenants_processed=2 ) logger.info("=" * 80) except Exception as e: logger.error("=" * 80) logger.error("❌ Demo AI Models Seeding Failed") logger.error("=" * 80) logger.error("Error: %s", str(e)) raise async def main(): """Main entry point""" logger.info("Demo AI Models Seed Script Starting") logger.info("Mode: %s", os.getenv("DEMO_MODE", "development")) logger.info("Log Level: %s", os.getenv("LOG_LEVEL", "INFO")) try: seeder = DemoAIModelSeeder() await seeder.seed_all_demo_models() logger.info("") logger.info("🎉 Success! Demo AI models are ready.") logger.info("") logger.info("Note: These are fake models for demo purposes only.") logger.info(" No actual model files exist on disk.") logger.info("") return 0 except Exception as e: logger.error("Demo AI models seed failed", error=str(e), exc_info=True) return 1 if __name__ == "__main__": exit_code = asyncio.run(main()) sys.exit(exit_code)