# 28 — Google One Tap SSO & Advanced Lead Capture

**Status:** Planning | **Depends on:** 01 (Geographic Extraction), 17 (Client Portal)
**Estimated effort:** 100 hours | **Phase:** During platform replacements | **Type:** Lead capture + authentication

---

## OVERVIEW

Google One Tap generated 228,000 leads for Luxury Presence in 2024 with zero friction — a single click, no form, instant authentication. This module adds Google One Tap, Apple Sign-In, and progressive profiling to all Fogbreak consumer-facing surfaces (property search, valuations, neighborhood guides, blog). Increases lead capture conversion by 40-60% while reducing friction.

**What it replaces:** Manual email/password login, clunky form fills. Improves upon existing RealScout replacement (instruction 09) with frictionless authentication.

**What it adds:**
- Google One Tap widget (auto-sign-in detection, no form)
- Apple Sign-In (iOS, iPad users)
- Progressive lead capture (soft gates, not hard gates — browse first, gate later)
- Smart form optimization (email from SSO, collect phone/timeline/budget over time)
- Returning visitor recognition (personalized feed, saved searches, recommendations)
- Lead routing automation (assign by location, source, availability, relationship)
- Conversion funnel tracking (view → engagement → prompt → authentication → CRM entry)
- A/B testing framework (test prompt timing, messaging, placement)

**Why this matters:** Luxury buyers are time-constrained. Every form field loses 30% of prospects. One-click auth + progressive profiling = luxury conversion. Returning visitors see personalized experience, increasing stickiness. Lead routing ensures assignment to right agent, increasing close rate.

---

## DATABASE SCHEMA

### Table: sso_sessions

Authenticated sessions from Google One Tap, Apple Sign-In, or manual email login.

```sql
CREATE TABLE sso_sessions (
  id INT PRIMARY KEY AUTO_INCREMENT,
  tenant_id INT,
  provider VARCHAR(50) DEFAULT 'google',

  -- User info from SSO
  email VARCHAR(255) NOT NULL,
  name VARCHAR(255),
  profile_photo_url VARCHAR(500),

  -- Session token for frontend
  session_token VARCHAR(255) UNIQUE NOT NULL,
  device_id VARCHAR(255),

  -- First vs returning visitor
  is_returning_visitor TINYINT DEFAULT 0,
  previous_session_count INT DEFAULT 0,

  -- Lead source tracking
  lead_source VARCHAR(100),
  landing_page VARCHAR(255),
  utm_source VARCHAR(100),
  utm_campaign VARCHAR(100),

  -- Session activity
  last_activity_at TIMESTAMP,
  session_duration_seconds INT,

  -- Expiry
  expires_at TIMESTAMP,

  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  FOREIGN KEY (tenant_id) REFERENCES tenants(id),
  UNIQUE (email, tenant_id),
  INDEX (session_token),
  INDEX (email),
  INDEX (created_at)
);
```

### Table: lead_capture_events

Funnel tracking: page view → engagement → prompt shown → authentication → CRM entry.

```sql
CREATE TABLE lead_capture_events (
  id INT PRIMARY KEY AUTO_INCREMENT,
  tenant_id INT,
  session_id INT REFERENCES sso_sessions(id),
  email VARCHAR(255),

  -- Funnel stage: 'page_view', 'engagement_start', 'prompt_shown', 'auth_initiated', 'auth_complete', 'lead_created'
  event_type VARCHAR(100) NOT NULL,

  -- Context: what triggered this event
  context_type VARCHAR(100),
  context_data JSON,

  -- Page/feature
  page_url VARCHAR(500),
  feature_name VARCHAR(100),

  -- Timing (for funnel analysis)
  time_since_session_start_seconds INT,
  time_to_next_event_seconds INT,

  -- A/B test variant (if applicable)
  ab_test_id INT,
  ab_test_variant VARCHAR(100),

  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  FOREIGN KEY (tenant_id) REFERENCES tenants(id),
  FOREIGN KEY (session_id) REFERENCES sso_sessions(id),
  INDEX (tenant_id, email),
  INDEX (event_type),
  INDEX (created_at)
);
```

### Table: progressive_profiles

Incrementally collected user data over multiple sessions.

```sql
CREATE TABLE progressive_profiles (
  id INT PRIMARY KEY AUTO_INCREMENT,
  tenant_id INT,
  email VARCHAR(255) NOT NULL,

  -- From SSO (always available)
  name VARCHAR(255),
  profile_photo_url VARCHAR(500),
  google_id VARCHAR(255),
  apple_id VARCHAR(255),

  -- Progressively collected (one per session usually)
  phone VARCHAR(20),
  phone_confirmed_at DATETIME,

  -- Buyer profile (from progressive gates)
  price_range_min DECIMAL(12,2),
  price_range_max DECIMAL(12,2),
  bedrooms INT,
  bathrooms DECIMAL(4,1),
  preferred_locations JSON,

  -- Timeline (when buying)
  timeline_months INT,
  timeline_confirmed_at DATETIME,

  -- Agent preference
  preferred_agent_id INT REFERENCES admins(id),

  -- Engagement level
  total_property_views INT DEFAULT 0,
  total_saved_searches INT DEFAULT 0,
  valuation_requests INT DEFAULT 0,

  -- Lead status in CRM
  client_id INT REFERENCES clients(id),
  lead_status VARCHAR(50),

  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  FOREIGN KEY (tenant_id) REFERENCES tenants(id),
  FOREIGN KEY (preferred_agent_id) REFERENCES admins(id),
  FOREIGN KEY (client_id) REFERENCES clients(id),
  UNIQUE (tenant_id, email),
  INDEX (email),
  INDEX (client_id)
);
```

### Table: lead_routing_rules

Define how to assign leads to agents.

```sql
CREATE TABLE lead_routing_rules (
  id INT PRIMARY KEY AUTO_INCREMENT,
  tenant_id INT,
  rule_name VARCHAR(255),

  -- Rule type: 'geography', 'lead_source', 'availability', 'relationship'
  rule_type VARCHAR(50),

  -- Routing logic (JSON for flexibility)
  rule_config JSON,

  -- Examples:
  -- { "type": "geography", "markets": ["Carmel-by-the-Sea", "Pacific Grove"], "assign_to": [10, 11], "round_robin": true }
  -- { "type": "lead_source", "source": "email_signup", "assign_to": [5], "priority": 1 }
  -- { "type": "existing_relationship", "check_type": "previous_client", "assign_to": "original_agent", "priority": 2 }

  -- Fallback
  fallback_agent_id INT REFERENCES admins(id),

  -- Enable/disable
  enabled TINYINT DEFAULT 1,
  priority INT DEFAULT 0,

  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  FOREIGN KEY (tenant_id) REFERENCES tenants(id),
  FOREIGN KEY (fallback_agent_id) REFERENCES admins(id),
  INDEX (tenant_id, priority DESC)
);
```

### Table: ab_tests

A/B testing framework for lead capture optimization.

```sql
CREATE TABLE ab_tests (
  id INT PRIMARY KEY AUTO_INCREMENT,
  tenant_id INT,
  test_name VARCHAR(255),
  test_type VARCHAR(100),

  -- Test type: 'prompt_timing', 'prompt_messaging', 'prompt_placement', 'gate_trigger'
  -- Examples: 'prompt_after_3_views', 'prompt_after_5_min', 'prompt_on_saved_search'

  variant_a_config JSON,
  variant_b_config JSON,

  -- Sample size
  target_sample_size INT,
  current_sample_size INT DEFAULT 0,

  -- Results (calculated)
  variant_a_conversions INT DEFAULT 0,
  variant_b_conversions INT DEFAULT 0,
  winner VARCHAR(10),
  confidence_level DECIMAL(5,2),

  -- Status: 'draft', 'active', 'completed', 'paused'
  status VARCHAR(50) DEFAULT 'draft',

  start_date DATETIME,
  end_date DATETIME,

  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  FOREIGN KEY (tenant_id) REFERENCES tenants(id),
  INDEX (tenant_id, status)
);
```

### Table: lead_capture_config

Tenant-specific configuration for progressive gates and prompt timing.

```sql
CREATE TABLE lead_capture_config (
  id INT PRIMARY KEY AUTO_INCREMENT,
  tenant_id INT,

  -- Gate strategy: 'none', 'soft_gates', 'progressive', 'hard_gate'
  gate_strategy VARCHAR(50) DEFAULT 'progressive',

  -- Soft gate trigger points
  prompt_after_property_views INT DEFAULT 3,
  prompt_after_saved_search TINYINT DEFAULT 1,
  prompt_after_time_seconds INT DEFAULT 300,
  prompt_on_valuation_request TINYINT DEFAULT 1,

  -- Messaging
  prompt_title VARCHAR(255) DEFAULT 'Get personalized listings',
  prompt_message TEXT,
  prompt_cta_text VARCHAR(100) DEFAULT 'Sign in for free',

  -- Placement
  prompt_placement VARCHAR(50) DEFAULT 'modal',

  -- Personalization
  show_personalized_feed TINYINT DEFAULT 1,
  show_recommended_properties TINYINT DEFAULT 1,

  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  FOREIGN KEY (tenant_id) REFERENCES tenants(id),
  UNIQUE (tenant_id)
);
```

---

## API ENDPOINTS

### POST /api/auth.php?action=googleOneTapVerify

Verify Google One Tap ID token and create session.

**Request:**
```json
{
  "token": "eyJhbGciOiJSUzI1NiIs...",
  "device_id": "device-abc-123",
  "landing_page": "/properties/search",
  "utm_source": "email",
  "utm_campaign": "march_launch"
}
```

**Response:**
```json
{
  "success": true,
  "data": {
    "session_token": "sess-xyz-789-long-string",
    "email": "buyer@example.com",
    "name": "John Doe",
    "profile_photo_url": "https://lh3.googleusercontent.com/...",
    "is_returning_visitor": true,
    "previous_session_count": 5
  },
  "message": "Sign-in successful"
}
```

**Implementation:**
- Verify Google token with Google's tokeninfo endpoint (cache validation for 60 sec)
- Extract email, name, sub (Google ID)
- Check if email exists in progressive_profiles (returning visitor)
- Insert or update sso_sessions
- Generate session_token (random 64-char)
- Record lead_capture_event (auth_complete)
- Return session_token for frontend storage (localStorage)

---

### POST /api/auth.php?action=appleSignInVerify

Verify Apple Sign-In token (similar to Google).

**Request:**
```json
{
  "identityToken": "eyJraWQiOi...",
  "user": {
    "name": { "firstName": "Jane", "lastName": "Doe" },
    "email": "jane@example.com"
  }
}
```

**Implementation:**
- Verify Apple token signature with Apple's public keys
- Extract email (may be relay if user chose privacy)
- Create sso_session + progressive_profile
- Log event

---

### POST /api/auth.php?action=captureLeadEvent

Record a lead funnel event.

**Request:**
```json
{
  "event_type": "property_view",
  "context_type": "saved_search",
  "page_url": "/properties/123-oceanfront-carmel",
  "feature_name": "property_detail_page",
  "ab_test_id": 5,
  "ab_test_variant": "prompt_after_3_views"
}
```

**Implementation:**
- Insert into lead_capture_events
- Calculate time_since_session_start
- If event is engagement trigger (saved_search, valuation_request), check if soft gate should fire
- Return decision: show_prompt_now: true/false

---

### GET /api/auth.php?action=getProgressiveProfile

Fetch current progressive profile for authenticated session.

**Response:**
```json
{
  "success": true,
  "data": {
    "email": "buyer@example.com",
    "name": "John Doe",
    "phone": "415-555-1234",
    "price_range_min": 5000000,
    "price_range_max": 8000000,
    "preferred_locations": ["Carmel-by-the-Sea", "Pacific Grove"],
    "timeline_months": 6,
    "total_property_views": 47,
    "total_saved_searches": 3,
    "profile_completeness": 75
  }
}
```

---

### PUT /api/auth.php?action=updateProfile

Update progressive profile with new data.

**Request:**
```json
{
  "phone": "415-555-1234",
  "price_range_min": 5000000,
  "price_range_max": 8000000,
  "timeline_months": 6,
  "preferred_locations": ["Carmel-by-the-Sea", "Pacific Grove"]
}
```

**Implementation:**
- Update progressive_profiles for current email
- Log as capture_event (profile_completed)
- Check if all required fields collected → trigger lead_created event
- If client_id not yet set, call createCRMLeadFromProfile

---

### POST /api/auth.php?action=configureCaptureRules

Admin endpoint: set gate strategy and prompt configuration.

**Request:**
```json
{
  "gate_strategy": "progressive",
  "prompt_after_property_views": 3,
  "prompt_after_saved_search": true,
  "prompt_title": "See all off-market listings",
  "prompt_message": "Sign in to access exclusive private listings.",
  "prompt_cta_text": "Access now"
}
```

**Implementation:**
- Update lead_capture_config for tenant_id
- Cache config in memory or Redis
- Apply to all active sessions immediately

---

### GET /api/auth.php?action=getConversionFunnel

Get conversion funnel metrics (admin dashboard).

**Query params:** `days` (default 30), `ab_test_id` (optional filter)

**Response:**
```json
{
  "success": true,
  "data": {
    "funnel": [
      { "stage": "page_view", "count": 5234 },
      { "stage": "engagement", "count": 892 },
      { "stage": "prompt_shown", "count": 723 },
      { "stage": "auth_initiated", "count": 156 },
      { "stage": "auth_complete", "count": 142 },
      { "stage": "lead_created", "count": 89 }
    ],
    "overall_conversion": 1.7,
    "by_ab_variant": {
      "prompt_after_3_views": { "shown": 423, "auth": 92, "conversion": 21.8 },
      "prompt_after_5_views": { "shown": 300, "auth": 64, "conversion": 21.3 }
    }
  }
}
```

**Implementation:**
- Query lead_capture_events group by event_type
- Calculate conversion rates (auth_complete / page_view * 100)
- Break down by AB test variant if active_tests

---

### POST /api/admin.php?action=createABTest

Create new A/B test for lead capture optimization.

**Request:**
```json
{
  "test_name": "Prompt timing comparison",
  "test_type": "prompt_timing",
  "variant_a_config": { "trigger": "after_3_views", "messaging": "standard" },
  "variant_b_config": { "trigger": "after_5_views", "messaging": "urgency" },
  "target_sample_size": 1000,
  "duration_days": 14
}
```

**Implementation:**
- Create ab_tests record
- Assign variants to sessions randomly (50/50)
- Log ab_test_id in lead_capture_events
- Track conversions by variant

---

### POST /api/admin.php?action=configureLeadRouting

Define lead routing rules.

**Request:**
```json
{
  "rule_name": "Carmel specialists",
  "rule_type": "geography",
  "rule_config": {
    "markets": ["Carmel-by-the-Sea"],
    "assign_to": [1, 2, 3],
    "round_robin": true
  },
  "fallback_agent_id": 5,
  "priority": 1
}
```

**Implementation:**
- Insert lead_routing_rules
- Load all rules on startup (cache in memory)
- Use during createCRMLeadFromProfile

---

### GET /api/auth.php?action=getABTestResults

Get results of a completed A/B test.

**Query params:** `test_id`

**Response:**
```json
{
  "success": true,
  "data": {
    "test_id": 7,
    "test_name": "Prompt timing comparison",
    "variant_a": {
      "label": "after_3_views",
      "sample_size": 502,
      "conversions": 89,
      "conversion_rate": 17.7
    },
    "variant_b": {
      "label": "after_5_views",
      "sample_size": 498,
      "conversions": 76,
      "conversion_rate": 15.3
    },
    "winner": "variant_a",
    "confidence_level": 92.5,
    "recommendation": "Use prompt after 3 views. 17.7% > 15.3% with 92.5% confidence."
  }
}
```

---

## IMPLEMENTATION STEPS

### Phase 1: Google One Tap Integration (Week 1)

1. **Add Google One Tap script to frontend** (portal.html, property search page)
   ```html
   <script src="https://accounts.google.com/gsi/client" async defer></script>
   <div id="g_id_onload"
        data-client_id="YOUR_GOOGLE_CLIENT_ID"
        data-callback="handleCredentialResponse">
   </div>
   <div class="g_id_signin" data-type="standard"></div>
   ```

2. **Implement frontend verification** (js/sso.js new module)
   ```javascript
   function handleCredentialResponse(response) {
     fetch('/api/auth.php?action=googleOneTapVerify', {
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
       body: JSON.stringify({
         token: response.credential,
         device_id: getOrCreateDeviceId(),
         landing_page: window.location.pathname,
         utm_source: new URLSearchParams(location.search).get('utm_source')
       })
     })
     .then(r => r.json())
     .then(data => {
       if (data.success) {
         localStorage.setItem('session_token', data.data.session_token);
         localStorage.setItem('email', data.data.email);
         loadPersonalizedFeed();
       }
     });
   }
   ```

3. **Implement backend verification** (api/auth.php → googleOneTapVerify)
   - Verify token with Google's tokeninfo endpoint
   - Extract email, name, Google ID
   - Create sso_session + progressive_profile
   - Return session_token
   - Use PDO prepared statements

4. **Add device ID persistence** (localStorage)
   - Generate UUID on first visit
   - Use for returning visitor tracking
   - Link to sso_sessions.device_id

---

### Phase 2: Progressive Lead Capture (Week 2)

5. **Create soft gate logic** (js/lead-gates.js new module)
   - Track property views in session (sessionStorage)
   - On reaching threshold (3 views, 5 min idle, etc.), show soft prompt
   - Prompt = modal with "Sign in for personalized recommendations"
   - Call captureLeadEvent(event_type='prompt_shown')
   - Dismiss → continue browsing (no gate)
   - Sign in → create session

6. **Implement progressive profiling**
   - Email from SSO (always)
   - Phone on second prompt (optional)
   - Price range on valuation request (soft gate)
   - Timeline on saved search (soft gate)
   - Use updateProfile endpoint to persist
   - Calculate profile_completeness (%)

7. **Create lead capture config UI** (fogbreak.html admin tab)
   - Gate strategy selector: none, soft_gates, progressive
   - Threshold sliders: views, time, actions
   - Prompt message editor
   - Save to lead_capture_config table

---

### Phase 3: Returning Visitor Personalization (Week 3)

8. **Implement returning visitor detection**
   - On sso_sessions creation, check progressive_profiles.created_at
   - If exists and created_at < 30 days, is_returning_visitor = 1
   - Count previous_session_count from sso_sessions query
   - Load personalized feed for returning visitors

9. **Build personalized feed** (js/personalized-feed.js)
   - Show saved searches at top
   - Show recently viewed properties
   - Show recommended properties (based on buyer_needs + location + price)
   - Recommend new listings matching buyer profile
   - Defer to instruction 09 (RealScout) for recommendation algorithm

10. **Implement session persistence**
    - localStorage stores session_token + email
    - On page load, validate session_token with getProgressiveProfile
    - If valid, auto-load personalized feed
    - If invalid, clear localStorage → show one-tap again

---

### Phase 4: Lead Routing & CRM Integration (Week 4)

11. **Create CRM lead from profile** (api/admin.php new function)
    ```php
    function createCRMLeadFromProfile($tenant_id, $email, $profile) {
      // 1. Check if client exists (email match)
      // 2. If not, create new client with progressive_profiles data
      // 3. Evaluate lead_routing_rules to assign agent
      // 4. Route based on: geography, source, availability, relationship
      // 5. Log routing decision in audit trail
      // 6. Send welcome email via email.php (from assigned agent)
      // 7. Enroll in welcome drip campaign (instruction 04)
      // 8. Update progressive_profiles.client_id
      // 9. Log lead_capture_event(type='lead_created')
    }
    ```

12. **Implement lead routing rules** (config-driven)
    - Support rule_type: geography, lead_source, existing_relationship, availability
    - Evaluate rules in priority order
    - Use round_robin for load balancing
    - Fallback to fallback_agent_id
    - Cache rules in memory (update on admin change)

13. **Add to email.php** (send welcome email)
    - Template: "Welcome {name}! I found {N} properties matching your profile."
    - Include 3-5 recommended properties (from personalized feed)
    - Include call-to-action: "Schedule showing", "View all matches"
    - Sign with assigned agent name + photo + contact

---

### Phase 5: Analytics & A/B Testing (Week 5)

14. **Implement conversion funnel tracking**
    - Log lead_capture_events at each funnel stage
    - Endpoint: getConversionFunnel for admin dashboard
    - Calculate funnel drop-off rates
    - Identify optimization opportunities

15. **Add A/B testing framework**
    - createABTest endpoint (admin)
    - Randomly assign variants to new sessions
    - Track conversions by variant
    - getABTestResults calculates winner + confidence
    - Statistical test: chi-square for categorical data

16. **Add dashboard metrics** (fogbreak.html)
    - Lead capture metrics card: views, auth, leads (last 30 days)
    - Conversion rate: overall + by source
    - A/B test results: current winner, confidence
    - Funnel visualization: drop-off chart
    - Lead routing: assignments by agent, by market

---

### Phase 6: Apple Sign-In & Advanced Features (Week 6)

17. **Add Apple Sign-In** (js/sso.js)
    - Add Apple Sign-In button (iOS/Safari users)
    - Verify identityToken with Apple's public keys
    - Similar flow to Google One Tap
    - Fallback email option (if user chooses privacy relay)

18. **Implement session expiry & refresh**
    - sso_sessions.expires_at = NOW() + 30 days
    - On page load, validate expiry
    - If expired, show login again
    - Optional: refresh token mechanism

19. **Add integration testing**
    - Create test Google/Apple accounts
    - Test full funnel: one-tap → profile → CRM lead
    - Verify lead routing works
    - Check funnel metrics accuracy
    - Test A/B variant assignment (50/50 split)

---

## DATABASE QUERIES (PDO Examples)

### Verify Google token and create session
```php
// 1. Verify with Google
$ch = curl_init('https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=' . urlencode($token));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch), true);
if ($response['aud'] !== GOOGLE_CLIENT_ID) throw new Exception('Invalid token');

$email = $response['email'];
$name = $response['name'] ?? '';
$google_id = $response['sub'];

// 2. Create/update sso_session
$stmt = $db->prepare("
  INSERT INTO sso_sessions (tenant_id, provider, email, name, profile_photo_url,
                            session_token, device_id, is_returning_visitor,
                            landing_page, utm_source, created_at, expires_at)
  VALUES (?, 'google', ?, ?, ?, ?, ?, ?, ?, ?, NOW(), DATE_ADD(NOW(), INTERVAL 30 DAY))
  ON DUPLICATE KEY UPDATE
    previous_session_count = previous_session_count + 1,
    is_returning_visitor = 1,
    last_activity_at = NOW()
");
$session_token = bin2hex(random_bytes(32));
$stmt->execute([$tenant_id, $email, $name, $photo_url, $session_token, $device_id, 1, $landing_page, $utm_source]);

// 3. Update or create progressive_profile
$stmt = $db->prepare("
  INSERT INTO progressive_profiles (tenant_id, email, name, profile_photo_url, google_id)
  VALUES (?, ?, ?, ?, ?)
  ON DUPLICATE KEY UPDATE name = VALUES(name), profile_photo_url = VALUES(profile_photo_url)
");
$stmt->execute([$tenant_id, $email, $name, $photo_url, $google_id]);
```

### Query conversion funnel
```php
$stmt = $db->prepare("
  SELECT event_type, COUNT(*) as count,
         ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM lead_capture_events WHERE tenant_id = ? AND DATE(created_at) >= DATE_SUB(NOW(), INTERVAL ? DAY)) * 100, 2) as pct
  FROM lead_capture_events
  WHERE tenant_id = ? AND DATE(created_at) >= DATE_SUB(NOW(), INTERVAL ? DAY)
  GROUP BY event_type
  ORDER BY created_at
");
$stmt->execute([$tenant_id, $days, $tenant_id, $days]);
$funnel = $stmt->fetchAll(PDO::FETCH_ASSOC);
```

### Get lead routing assignment
```php
$stmt = $db->prepare("
  SELECT * FROM lead_routing_rules
  WHERE tenant_id = ? AND enabled = 1
  ORDER BY priority DESC
");
$stmt->execute([$tenant_id]);
$rules = $stmt->fetchAll(PDO::FETCH_ASSOC);

foreach ($rules as $rule) {
  if (evaluateRule($rule, $lead_data)) {
    return assignToAgents($rule['rule_config']['assign_to'], $rule['rule_config']['round_robin']);
  }
}
return assignToAgent($default_agent_id);
```

---

## TESTING CRITERIA

### Functional Tests

- [ ] Google One Tap loads and auto-fills profile
- [ ] Session token created and persists in localStorage
- [ ] Returning visitor detected (is_returning_visitor = 1)
- [ ] Soft gate fires after 3 property views (or configured threshold)
- [ ] Soft gate shows once per session (no spam)
- [ ] Progressive profile updates (phone, price range, timeline) persist
- [ ] Personalized feed shows saved searches + recommendations
- [ ] Lead routing assigns to correct agent based on geography
- [ ] A/B test assigns variants 50/50
- [ ] Conversion funnel metrics accumulate correctly
- [ ] Welcome email sends from assigned agent
- [ ] Session expires after 30 days (or configured)

### Security Tests

- [ ] SQL injection on email, name, all inputs (prepared statements)
- [ ] Cross-tenant data leak (all queries filtered by tenant_id)
- [ ] Session token validation on protected endpoints
- [ ] Google token signature verification (tokeninfo endpoint)
- [ ] Device ID cannot be spoofed (tied to session)
- [ ] Unauthorized access to funnel/routing admin endpoints (admin role check)

### Performance Tests

- [ ] One Tap loads < 500ms
- [ ] Google token verification < 200ms (with caching)
- [ ] Personalized feed renders < 1s (50 properties)
- [ ] Lead routing decision < 100ms
- [ ] Funnel query < 500ms (30 days, 10k events)

### Conversion Tests

- [ ] Funnel conversion rate ≥ 15% (one tap → lead created)
- [ ] A/B test detects 10% difference in conversion (500 sample size, 90% confidence)
- [ ] Returning visitors have 2x+ conversion vs first-time

---

## INTEGRATION POINTS

- **auth.php** — Session management, token validation
- **admin.php** — CRM lead creation from profile, lead routing
- **email.php** — Welcome email sending (Fair Housing audit)
- **drips.php** — Auto-enroll in welcome campaign
- **properties.php** — Personalized recommendations (leverage RealScout replacement)
- **clients.php** — Create client from progressive profile
- **fogbreak.html** — Admin dashboard (metrics, A/B testing, config)
- **portal.html** — Consumer-facing property search with gates + one-tap
- **tenants.php** — Lead capture config per tenant

---

## OPERATIONAL NOTES

- **One Tap is a baseline.** It's the easiest auth path. Progressive profiling collects more detail over time (less friction upfront).
- **Prompt fatigue is real.** Don't show soft gate more than once per session. Track dismissals.
- **Lead routing is configurable.** Start simple (by geography). Add complexity as needed. Use round-robin to prevent agent overload.
- **A/B testing requires sample size.** With 1,000 samples, can detect 5% difference at 95% confidence. Plan test duration accordingly.
- **Returning visitor experience should delight.** Personalization is the payoff for giving up privacy. Show them we remember (saved searches, recommendations).
- **Fair Housing audit on all drip emails.** Even automated welcome emails.
- **Analytics are the feedback loop.** Use conversion funnel to optimize. Test messaging, timing, placement continuously.

---

End of instruction 28.
