Files
bakery-ia/services/demo_session/README.md

686 lines
21 KiB
Markdown
Raw Normal View History

2025-11-06 14:10:04 +01:00
# 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.**