686 lines
21 KiB
Markdown
686 lines
21 KiB
Markdown
|
|
# 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.**
|