# Demo Session Service

## Overview

The **Demo Session Service** creates ephemeral, isolated demo environments for sales demonstrations and prospect trials. It provisions temporary tenants with pre-seeded realistic bakery data, allowing prospects to explore the full platform without affecting production data. Demo sessions automatically expire after a configurable period (default: 24 hours) and are completely isolated from real customer tenants, making it safe for prospects to experiment freely.

## Key Features

### Demo Environment Provisioning
- **One-Click Demo Creation** - Create demo tenant in seconds
- **Pre-Seeded Data** - Realistic sales, inventory, forecast data
- **Isolated Tenants** - Complete separation from production
- **Temporary Credentials** - Auto-generated demo user accounts
- **Configurable Duration** - 1 hour to 7 days (default: 24 hours)
- **Instant Access** - No email verification required

### Realistic Demo Data
- **90 Days Sales History** - Realistic transaction patterns
- **Product Catalog** - 20+ common bakery products
- **Inventory** - Current stock levels and movements
- **Forecasts** - Pre-generated 7-day forecasts
- **Production Schedules** - Sample production plans
- **Suppliers** - 5+ sample supplier profiles
- **Team Members** - Sample staff with different roles

### Demo Scenarios
- **Standard Bakery** - Small neighborhood bakery (1 location)
- **Multi-Location** - Bakery chain (3 locations)
- **High-Volume** - Large production bakery
- **Custom Scenario** - Configurable for specific prospects
- **Spanish Locale** - Madrid-based bakery examples
- **Feature Showcase** - Highlight specific capabilities

### Session Management
- **Auto-Expiration** - Automatic cleanup after expiry
- **Session Extension** - Extend active demos
- **Session Termination** - Manually end demo
- **Session Analytics** - Track demo engagement
- **Concurrent Limits** - Prevent resource abuse
- **IP-Based Tracking** - Monitor demo usage

### Sales Enablement
- **Demo Link Generation** - Shareable demo URLs
- **Sales Dashboard** - Track active demos
- **Usage Analytics** - Feature engagement metrics
- **Lead Tracking** - Connect demos to CRM
- **Conversion Tracking** - Demo to trial to paid
- **Performance Metrics** - Demo success rates

### Security & Isolation
- **Tenant Isolation** - Complete data separation
- **Resource Limits** - Prevent abuse
- **Auto-Cleanup** - Remove expired demos
- **No Production Access** - Isolated database/environment
- **Rate Limiting** - Prevent demo spam
- **Audit Logging** - Track all demo activities

## Business Value

### For Sales Team
- **Instant Demos** - No setup time, always ready
- **Realistic Experience** - Prospects see real functionality
- **Risk-Free** - Prospects can't break anything
- **Consistent** - Every demo shows same quality data
- **Scalable** - Handle 100+ concurrent demos
- **Self-Service** - Prospects can explore independently

### Quantifiable Impact
- **Sales Cycle**: 30-50% shorter with live demos
- **Conversion Rate**: 2-3× higher vs. screenshots/videos
- **Demo Setup Time**: 0 minutes vs. 15-30 minutes manual
- **Lead Quality**: Higher engagement indicates serious interest
- **Sales Efficiency**: 5-10× more demos per sales rep
- **Cost Savings**: €500-1,500/month (sales time saved)

### For Prospects
- **Try Before Buy**: Experience platform hands-on
- **No Commitment**: No credit card, no sign-up friction
- **Immediate Access**: Start exploring in 30 seconds
- **Realistic Data**: Understand real-world value
- **Self-Paced**: Explore at own speed
- **Safe Environment**: Can't break or affect anything

## Technology Stack

- **Framework**: FastAPI (Python 3.11+) - Async web framework
- **Database**: PostgreSQL 17 - Demo session tracking
- **Demo DB**: Separate PostgreSQL - Isolated demo data
- **Caching**: Redis 7.4 - Session cache, rate limiting
- **Messaging**: RabbitMQ 4.1 - Cleanup events
- **Data Seeding**: Faker, custom data generators
- **ORM**: SQLAlchemy 2.0 (async) - Database abstraction
- **Logging**: Structlog - Structured JSON logging
- **Metrics**: Prometheus Client - Demo metrics

## API Endpoints (Key Routes)

### Demo Session Management
- `POST /api/v1/demo-sessions` - Create new demo session
- `GET /api/v1/demo-sessions/{session_id}` - Get session details
- `POST /api/v1/demo-sessions/{session_id}/extend` - Extend session
- `DELETE /api/v1/demo-sessions/{session_id}` - Terminate session
- `GET /api/v1/demo-sessions/{session_id}/credentials` - Get login credentials
- `GET /api/v1/demo-sessions/active` - List active sessions

### Demo Scenarios
- `GET /api/v1/demo-sessions/scenarios` - List available scenarios
- `GET /api/v1/demo-sessions/scenarios/{scenario_id}` - Get scenario details
- `POST /api/v1/demo-sessions/scenarios/{scenario_id}/create` - Create session from scenario

### Sales Dashboard (Internal)
- `GET /api/v1/demo-sessions/analytics/dashboard` - Demo analytics
- `GET /api/v1/demo-sessions/analytics/usage` - Usage patterns
- `GET /api/v1/demo-sessions/analytics/conversion` - Demo to signup conversion

### Health & Monitoring
- `GET /api/v1/demo-sessions/health` - Service health
- `GET /api/v1/demo-sessions/cleanup/status` - Cleanup job status

## Database Schema

### Main Tables

**demo_sessions**
```sql
CREATE TABLE demo_sessions (
    id UUID PRIMARY KEY,
    session_token VARCHAR(255) UNIQUE NOT NULL,
    demo_tenant_id UUID NOT NULL,              -- Demo tenant in separate DB

    -- Configuration
    scenario_name VARCHAR(100) NOT NULL,       -- standard_bakery, multi_location, etc.
    duration_hours INTEGER DEFAULT 24,

    -- Status
    status VARCHAR(50) DEFAULT 'active',       -- active, extended, expired, terminated
    created_at TIMESTAMP DEFAULT NOW(),
    expires_at TIMESTAMP NOT NULL,
    extended_count INTEGER DEFAULT 0,
    terminated_at TIMESTAMP,
    termination_reason VARCHAR(255),

    -- Tracking
    created_by_ip INET,
    user_agent TEXT,
    referrer VARCHAR(500),
    utm_source VARCHAR(100),
    utm_campaign VARCHAR(100),
    utm_medium VARCHAR(100),

    -- Usage analytics
    login_count INTEGER DEFAULT 0,
    last_activity_at TIMESTAMP,
    page_views INTEGER DEFAULT 0,
    features_used JSONB,                       -- Array of feature names

    -- Lead info (if provided)
    lead_email VARCHAR(255),
    lead_name VARCHAR(255),
    lead_phone VARCHAR(50),
    lead_company VARCHAR(255),

    INDEX idx_sessions_status ON (status, expires_at),
    INDEX idx_sessions_token ON (session_token)
);
```

**demo_scenarios**
```sql
CREATE TABLE demo_scenarios (
    id UUID PRIMARY KEY,
    scenario_name VARCHAR(100) UNIQUE NOT NULL,
    display_name VARCHAR(255) NOT NULL,
    description TEXT,

    -- Configuration
    business_name VARCHAR(255),
    location_count INTEGER DEFAULT 1,
    product_count INTEGER DEFAULT 20,
    days_of_history INTEGER DEFAULT 90,

    -- Features to highlight
    featured_capabilities JSONB,

    -- Data generation settings
    seed_data_config JSONB,

    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT NOW()
);
```

**demo_session_events**
```sql
CREATE TABLE demo_session_events (
    id UUID PRIMARY KEY,
    session_id UUID REFERENCES demo_sessions(id) ON DELETE CASCADE,
    event_type VARCHAR(100) NOT NULL,          -- login, page_view, feature_used, action_performed
    event_data JSONB,
    ip_address INET,
    occurred_at TIMESTAMP DEFAULT NOW(),
    INDEX idx_session_events_session (session_id, occurred_at)
);
```

**demo_session_metrics**
```sql
CREATE TABLE demo_session_metrics (
    id UUID PRIMARY KEY,
    metric_date DATE NOT NULL,
    scenario_name VARCHAR(100),

    -- Volume
    sessions_created INTEGER DEFAULT 0,
    sessions_completed INTEGER DEFAULT 0,      -- Not terminated early
    sessions_expired INTEGER DEFAULT 0,
    sessions_terminated INTEGER DEFAULT 0,

    -- Engagement
    avg_duration_minutes INTEGER,
    avg_login_count DECIMAL(5, 2),
    avg_page_views DECIMAL(5, 2),
    avg_features_used DECIMAL(5, 2),

    -- Conversion
    demo_to_signup_count INTEGER DEFAULT 0,
    conversion_rate_percentage DECIMAL(5, 2),

    calculated_at TIMESTAMP DEFAULT NOW(),
    UNIQUE(metric_date, scenario_name)
);
```

### Indexes for Performance
```sql
CREATE INDEX idx_sessions_expires ON demo_sessions(expires_at) WHERE status = 'active';
CREATE INDEX idx_sessions_scenario ON demo_sessions(scenario_name, created_at DESC);
CREATE INDEX idx_events_session_type ON demo_session_events(session_id, event_type);
```

## Business Logic Examples

### Demo Session Creation
```python
async def create_demo_session(
    scenario_name: str = 'standard_bakery',
    duration_hours: int = 24,
    lead_info: dict = None,
    request_info: dict = None
) -> DemoSession:
    """
    Create new demo session with pre-seeded data.
    """
    # Get scenario configuration
    scenario = await db.query(DemoScenario).filter(
        DemoScenario.scenario_name == scenario_name,
        DemoScenario.is_active == True
    ).first()

    if not scenario:
        raise ValueError("Invalid scenario")

    # Check concurrent demo limit
    active_demos = await db.query(DemoSession).filter(
        DemoSession.status == 'active',
        DemoSession.expires_at > datetime.utcnow()
    ).count()

    if active_demos >= MAX_CONCURRENT_DEMOS:
        raise Exception("Maximum concurrent demos reached")

    try:
        # Generate session token
        session_token = secrets.token_urlsafe(32)

        # Create demo tenant in separate database
        demo_tenant = await create_demo_tenant(scenario)

        # Seed demo data
        await seed_demo_data(demo_tenant.id, scenario)

        # Create session record
        session = DemoSession(
            session_token=session_token,
            demo_tenant_id=demo_tenant.id,
            scenario_name=scenario_name,
            duration_hours=duration_hours,
            expires_at=datetime.utcnow() + timedelta(hours=duration_hours),
            created_by_ip=request_info.get('ip'),
            user_agent=request_info.get('user_agent'),
            referrer=request_info.get('referrer'),
            utm_source=request_info.get('utm_source'),
            utm_campaign=request_info.get('utm_campaign'),
            lead_email=lead_info.get('email') if lead_info else None,
            lead_name=lead_info.get('name') if lead_info else None
        )

        db.add(session)

        # Log event
        event = DemoSessionEvent(
            session_id=session.id,
            event_type='session_created',
            event_data={'scenario': scenario_name},
            ip_address=request_info.get('ip')
        )
        db.add(event)

        await db.commit()

        logger.info("Demo session created",
                   session_id=str(session.id),
                   scenario=scenario_name,
                   duration_hours=duration_hours)

        # Publish event
        await publish_event('demo_sessions', 'demo.session_created', {
            'session_id': str(session.id),
            'scenario': scenario_name
        })

        return session

    except Exception as e:
        logger.error("Demo session creation failed",
                    scenario=scenario_name,
                    error=str(e))
        raise

async def create_demo_tenant(scenario: DemoScenario) -> DemoTenant:
    """
    Create isolated demo tenant in demo database.
    """
    # Use separate database connection for demo data
    demo_db = get_demo_database_connection()

    tenant = DemoTenant(
        tenant_name=scenario.business_name or "Demo Bakery",
        email=f"demo_{uuid.uuid4().hex[:8]}@bakery-ia.com",
        status='demo',
        subscription_tier='pro',  # Always show Pro features in demo
        is_demo=True
    )

    demo_db.add(tenant)
    await demo_db.commit()

    return tenant

async def seed_demo_data(tenant_id: UUID, scenario: DemoScenario):
    """
    Seed demo tenant with realistic data.
    """
    demo_db = get_demo_database_connection()

    # Seed configuration
    config = scenario.seed_data_config or {}
    product_count = config.get('product_count', 20)
    days_of_history = config.get('days_of_history', 90)

    # 1. Seed product catalog
    products = await seed_products(demo_db, tenant_id, product_count)

    # 2. Seed suppliers
    suppliers = await seed_suppliers(demo_db, tenant_id, 5)

    # 3. Seed inventory
    await seed_inventory(demo_db, tenant_id, products, suppliers)

    # 4. Seed sales history (90 days)
    await seed_sales_history(demo_db, tenant_id, products, days_of_history)

    # 5. Generate forecasts
    await seed_forecasts(demo_db, tenant_id, products)

    # 6. Seed production schedules
    await seed_production_schedules(demo_db, tenant_id, products)

    # 7. Seed team members
    await seed_team_members(demo_db, tenant_id)

    logger.info("Demo data seeded",
               tenant_id=str(tenant_id),
               products=len(products),
               suppliers=len(suppliers))

async def seed_sales_history(
    demo_db,
    tenant_id: UUID,
    products: list,
    days: int = 90
) -> list:
    """
    Generate realistic sales history using patterns.
    """
    from faker import Faker
    fake = Faker('es_ES')  # Spanish locale

    sales_records = []
    start_date = date.today() - timedelta(days=days)

    for day_offset in range(days):
        current_date = start_date + timedelta(days=day_offset)
        is_weekend = current_date.weekday() >= 5
        is_holiday = await is_spanish_holiday(current_date)

        # Adjust volume based on day type
        base_transactions = 50
        if is_weekend:
            base_transactions = int(base_transactions * 1.4)  # 40% more on weekends
        if is_holiday:
            base_transactions = int(base_transactions * 0.7)  # 30% less on holidays

        # Add randomness
        daily_transactions = int(base_transactions * random.uniform(0.8, 1.2))

        for _ in range(daily_transactions):
            # Random product
            product = random.choice(products)

            # Realistic quantity (most orders are 1-5 units)
            quantity = random.choices([1, 2, 3, 4, 5, 6, 10], weights=[40, 25, 15, 10, 5, 3, 2])[0]

            # Calculate price with small variance
            unit_price = product.price * random.uniform(0.95, 1.05)

            sale = DemoSale(
                tenant_id=tenant_id,
                sale_date=current_date,
                sale_time=fake.time(),
                product_id=product.id,
                product_name=product.name,
                quantity=quantity,
                unit_price=unit_price,
                total_amount=quantity * unit_price,
                channel='pos'
            )

            sales_records.append(sale)

    # Bulk insert
    demo_db.bulk_save_objects(sales_records)
    await demo_db.commit()

    return sales_records
```

### Auto-Cleanup Job
```python
async def cleanup_expired_demos():
    """
    Background job to cleanup expired demo sessions.
    Runs every hour.
    """
    # Find expired sessions
    expired_sessions = await db.query(DemoSession).filter(
        DemoSession.status == 'active',
        DemoSession.expires_at <= datetime.utcnow()
    ).all()

    for session in expired_sessions:
        try:
            # Mark session as expired
            session.status = 'expired'
            session.terminated_at = datetime.utcnow()

            # Delete demo tenant and all data
            await delete_demo_tenant(session.demo_tenant_id)

            # Log event
            event = DemoSessionEvent(
                session_id=session.id,
                event_type='session_expired',
                occurred_at=datetime.utcnow()
            )
            db.add(event)

            logger.info("Demo session cleaned up",
                       session_id=str(session.id),
                       duration_hours=(session.terminated_at - session.created_at).total_seconds() / 3600)

        except Exception as e:
            logger.error("Demo cleanup failed",
                        session_id=str(session.id),
                        error=str(e))
            continue

    await db.commit()

    logger.info("Demo cleanup completed",
               expired_count=len(expired_sessions))
```

## Events & Messaging

### Published Events (RabbitMQ)

**Exchange**: `demo_sessions`
**Routing Keys**: `demo.session_created`, `demo.session_converted`

**Demo Session Created Event**
```json
{
    "event_type": "demo_session_created",
    "session_id": "uuid",
    "scenario": "standard_bakery",
    "duration_hours": 24,
    "lead_email": "prospect@example.com",
    "utm_source": "google_ads",
    "timestamp": "2025-11-06T10:00:00Z"
}
```

**Demo Converted to Signup**
```json
{
    "event_type": "demo_session_converted",
    "session_id": "uuid",
    "tenant_id": "uuid",
    "scenario": "standard_bakery",
    "demo_duration_hours": 2.5,
    "features_used": ["forecasting", "inventory", "production"],
    "timestamp": "2025-11-06T12:30:00Z"
}
```

## Custom Metrics (Prometheus)

```python
# Demo session metrics
demo_sessions_created_total = Counter(
    'demo_sessions_created_total',
    'Total demo sessions created',
    ['scenario']
)

demo_sessions_active = Gauge(
    'demo_sessions_active',
    'Current active demo sessions',
    []
)

demo_session_duration_hours = Histogram(
    'demo_session_duration_hours',
    'Demo session duration',
    ['scenario'],
    buckets=[0.5, 1, 2, 4, 8, 12, 24, 48]
)

demo_to_signup_conversions_total = Counter(
    'demo_to_signup_conversions_total',
    'Demo sessions that converted to signup',
    ['scenario']
)

demo_feature_usage_total = Counter(
    'demo_feature_usage_total',
    'Feature usage in demos',
    ['feature_name']
)
```

## Configuration

### Environment Variables

**Service Configuration:**
- `PORT` - Service port (default: 8019)
- `DATABASE_URL` - Main PostgreSQL connection
- `DEMO_DATABASE_URL` - Isolated demo database
- `REDIS_URL` - Redis connection string
- `RABBITMQ_URL` - RabbitMQ connection string

**Demo Configuration:**
- `DEFAULT_DEMO_DURATION_HOURS` - Default duration (default: 24)
- `MAX_DEMO_DURATION_HOURS` - Maximum duration (default: 168/7 days)
- `MAX_CONCURRENT_DEMOS` - Concurrent limit (default: 100)
- `CLEANUP_INTERVAL_MINUTES` - Cleanup frequency (default: 60)

**Data Seeding:**
- `DEMO_SALES_HISTORY_DAYS` - Sales history length (default: 90)
- `DEMO_PRODUCT_COUNT` - Number of products (default: 20)
- `DEMO_SUPPLIER_COUNT` - Number of suppliers (default: 5)

## Development Setup

### Prerequisites
- Python 3.11+
- PostgreSQL 17 (2 databases: main + demo)
- Redis 7.4
- RabbitMQ 4.1

### Local Development
```bash
cd services/demo_session
python -m venv venv
source venv/bin/activate

pip install -r requirements.txt

export DATABASE_URL=postgresql://user:pass@localhost:5432/demo_session
export DEMO_DATABASE_URL=postgresql://user:pass@localhost:5432/demo_data
export REDIS_URL=redis://localhost:6379/0
export RABBITMQ_URL=amqp://guest:guest@localhost:5672/

alembic upgrade head
python main.py
```

## Integration Points

### Dependencies
- **Separate Demo Database** - Isolated demo tenant data
- **Auth Service** - Demo user credentials
- **Data Generators** - Realistic data seeding
- **PostgreSQL** - Session tracking
- **Redis** - Rate limiting, caching
- **RabbitMQ** - Event publishing

### Dependents
- **Sales Team** - Demo creation
- **Marketing** - Landing page demos
- **Frontend** - Demo UI access
- **Analytics** - Demo conversion tracking

## Business Value for VUE Madrid

### Problem Statement
Traditional sales demos are difficult:
- Time-consuming setup (15-30 minutes per demo)
- Risk of breaking things in front of prospects
- Inconsistent demo quality
- No self-service for prospects
- Hard to track engagement
- Limited by sales rep availability

### Solution
Bakery-IA Demo Session Service provides:
- **Instant Demos**: Ready in 30 seconds
- **Risk-Free**: Isolated environments
- **Self-Service**: Prospects explore independently
- **Consistent Quality**: Same data every time
- **Engagement Tracking**: Know what prospects care about
- **Scalable**: Unlimited concurrent demos

### Quantifiable Impact

**Sales Efficiency:**
- 30-50% shorter sales cycle with live demos
- 2-3× conversion rate vs. static presentations
- 5-10× more demos per sales rep
- 0 minutes setup time vs. 15-30 minutes
- €500-1,500/month sales time saved

**Lead Quality:**
- Higher engagement = more qualified leads
- Feature usage indicates specific needs
- Demo-to-trial conversion: 35-45%
- Trial-to-paid conversion: 25-35%
- Overall demo-to-paid: 12-16%

**Marketing Value:**
- Self-service demos on landing page
- 24/7 availability for global prospects
- Viral potential (shareable demo links)
- Lower customer acquisition cost
- Better understanding of product-market fit

### Target Market Fit (Spanish Bakeries)
- **Visual Learners**: Spanish business culture values demonstrations
- **Trust Building**: Try-before-buy reduces risk perception
- **Language**: Demo data in Spanish increases resonance
- **Realistic**: Spanish products, Madrid locations feel authentic

### ROI for Platform
**Investment**: €100-300/month (compute + storage for demos)
**Value Generated**:
- 50+ demos/month → 20 trials → 6 paid customers
- 6 customers × €66 avg MRR = €396/month
- **Payback**: 1-3 months
- **ROI**: 30-400% depending on conversion rates

---

**Copyright © 2025 Bakery-IA. All rights reserved.**
