Skip to main content

Dashboard BigQuery Integration Playbook

Date Created: 2025-10-20
Purpose: Step-by-step guide to connect dashboard views to live BigQuery data with correct commission calculations

Overview

This playbook documents the process of migrating a dashboard from mock/fallback data to live BigQuery data. It was successfully applied to the CEO dashboard and can be replicated for Agent, Admin, and other dashboard views.

Key Success Criteria

  • ✅ Dashboard pulls real-time data from BigQuery
  • ✅ Commission calculations match canonical business logic
  • ✅ Historical data (2024+) is accessible
  • ✅ No hardcoded fallbacks or mock data
  • ✅ Proper error handling and logging
  • ✅ TypeScript type safety maintained

Phase 1: Understand the Current State

1.1 Identify Data Sources

Questions to answer:
  • Where is the dashboard currently getting data? (Mock data, CSV files, old BigQuery views?)
  • What BigQuery views/tables exist for this dashboard?
  • What is the canonical data model and calculation logic?
Tools:
# Find where data is being fetched
grep -r "get_.*_metrics" api/
grep -r "Mock\|mock\|fallback" api/

# Check existing BigQuery views
bq ls payroll_analytics
bq show --schema payroll_analytics.ceo_snapshot

1.2 Document Canonical Calculations

Example from CEO Dashboard:
  • Owner Commission = Gross Payout - Chargebacks - Agent Commissions
  • NOT a simple 20/80 split of gross
Where to look:
  • stage4_outputs/export_excel.py - Contains authoritative calculation logic
  • ENHANCED_CHURN_WORKBOOK_CANONICAL_SPEC.md - Business logic documentation
  • Existing Excel reports - Reverse engineer if needed

1.3 Check API Logs

# Identify which functions are being called
gcloud run services logs read fastapi-backend --region=us-central1 --limit=20

# Look for:
# - "Using mock data" warnings
# - "No data found" errors
# - Which BigQuery tables/views are being queried

Phase 2: Fix the BigQuery View

2.1 Review Current View Schema

# Get the view definition
bq show --format=prettyjson payroll_analytics.ceo_snapshot > current_view.json

# Read the SQL
cat integration/bigquery/sql/views/analytics_ceo_snapshot.sql

2.2 Identify Incorrect Calculations

Common Issues:
  • ❌ Hardcoded date filters (e.g., WHERE period_label >= '2025-01-01')
  • ❌ Incorrect commission formulas (e.g., gross_payout * 0.2)
  • ❌ Missing fields needed by API
  • ❌ Wrong table joins or filters
CEO Dashboard Example:
-- WRONG: Calculated in view
business_owner_commission = gross_payout * 0.2

-- CORRECT: Let API calculate from raw data
-- View should only provide: gross_payout, chargebacks, net_payout

2.3 Update View to Provide Raw Data

Principle: Views should provide raw aggregated data; API should perform business logic calculations.
-- Example: Simplified CEO snapshot view
CREATE OR REPLACE VIEW `payroll-bi-gauntlet.payroll_analytics.ceo_snapshot` AS
WITH stage1_aggregated AS (
  SELECT
    tenant_id,
    period_label,
    COUNT(DISTINCT business_label) as total_businesses,
    COUNT(DISTINCT member_id) as total_employees,
    SUM(credit) as gross_payout,
    SUM(debit) as chargebacks,
    SUM(total) as net_payout
  FROM `payroll-bi-gauntlet.payroll_raw.stage1_snapshots`
  WHERE payee_agent_id IN (721995, 668078, 0)  -- Owner IDs
    AND business_label IS NOT NULL
    AND period_label >= '2024-01-01'  -- Include historical data
  GROUP BY tenant_id, period_label
)
SELECT
  tenant_id,
  period_label,
  total_businesses,
  total_employees,
  gross_payout,
  chargebacks,
  net_payout
FROM stage1_aggregated
ORDER BY period_label DESC;

2.4 Deploy Updated View

# Test the SQL locally first
bq query --use_legacy_sql=false --dry_run < integration/bigquery/sql/views/analytics_ceo_snapshot.sql

# Deploy the view
Get-Content integration/bigquery/sql/views/analytics_ceo_snapshot.sql -Raw | bq query --use_legacy_sql=false

# Verify data exists
bq query --use_legacy_sql=false "
SELECT period_label, total_businesses, total_employees, gross_payout 
FROM payroll-bi-gauntlet.payroll_analytics.ceo_snapshot 
WHERE period_label IN ('2024-03-01', '2025-03-01') 
ORDER BY period_label;"

Phase 3: Update API Backend Logic

3.1 Locate the Data Fetching Function

Common locations:
  • api/routes/<dashboard>_metrics.py - FastAPI endpoint
  • api/bigquery/queries.py - BigQuery query functions
  • main.py - Legacy endpoints (check for duplicates!)
CEO Dashboard had TWO functions:
  1. api/bigquery/queries.py::get_ceo_metrics() - Not being used ❌
  2. main.py::get_ceo_metrics_simple() - Actually being called ✅

3.2 Fix Query to Use Correct View

Before:
# WRONG: Querying old table
query = f"""
SELECT * 
FROM `{project_id}.commissions.ceo_snapshots`
WHERE period = DATE('{period_label}')
"""
After:
# CORRECT: Using updated view
query = f"""
SELECT 
    total_businesses,
    total_employees,
    gross_payout,
    chargebacks,
    net_payout
FROM `{project_id}.payroll_analytics.ceo_snapshot`
WHERE period_label = DATE('{period_label}')
  AND tenant_id = '{tenant_id}'
"""

3.3 Implement Canonical Calculation Logic

Multi-Query Approach for Complex Calculations:
def get_ceo_metrics_simple(tenant_id: str, period_label: str):
    """CEO metrics using canonical calculation logic"""
    client = bigquery.Client(project=project_id)
    
    # Query 1: Get base metrics from view
    ceo_query = f"""
    SELECT 
        total_businesses,
        total_employees,
        gross_payout,
        chargebacks,
        net_payout
    FROM `{project_id}.payroll_analytics.ceo_snapshot`
    WHERE period_label = DATE('{period_label}')
      AND tenant_id = '{tenant_id}'
    """
    
    ceo_results = list(client.query(ceo_query).result())
    if not ceo_results:
        logger.warning(f"No data found for {period_label}")
        return None
    
    row = ceo_results[0]
    
    # Query 2: Get agent commissions for owner commission calculation
    agent_query = f"""
    SELECT
        SUM(agent_total) as agent_commissions
    FROM `{project_id}.payroll_processed.stage3_snapshots`
    WHERE period_label = DATE('{period_label}')
      AND tenant_id = '{tenant_id}'
      AND tpa_applied = TRUE
    """
    
    agent_results = list(client.query(agent_query).result())
    agent_commissions = float(agent_results[0].agent_commissions or 0) if agent_results and agent_results[0].agent_commissions else 0
    
    # Calculate owner commission using CANONICAL formula
    # Owner Commission = Gross - Chargebacks - Agent Commissions
    gross_payout = float(row.gross_payout or 0)
    chargebacks = float(row.chargebacks or 0)
    business_owner_commission = gross_payout - chargebacks - agent_commissions
    
    return {
        "period_label": period_label,
        "tenant_id": tenant_id,
        "total_businesses": int(row.total_businesses or 0),
        "total_employees": int(row.total_employees or 0),
        "gross_payout": gross_payout,
        "business_owner_commission": business_owner_commission,
        "total_commission": agent_commissions,
        "total_chargebacks": chargebacks,
        # Calculated fields
        "avg_commission_per_policy": gross_payout / max(int(row.total_businesses or 1), 1),
        "avg_commission_per_life": gross_payout / max(int(row.total_employees or 1), 1)
    }

YTD/QTD Query Patterns (Stage 10)

Overview

YTD (Year-to-Date) and QTD (Quarter-to-Date) queries aggregate data across multiple periods instead of a single period. The implementation uses a hybrid query approach that optimizes performance by combining pre-aggregated views with raw data queries:
  • Financial metrics: Query ceo_snapshot view (can SUM pre-aggregated values for faster queries)
  • Distinct counts: Query stage1_snapshots directly (must query raw data for accurate distinct counts)
  • Active businesses: Query stage1_snapshots for end period only (shows current state)

YTD Query Pattern

Purpose: Aggregate data from January 1 of the year to the selected period. Implementation: Uses three separate queries that are merged: Query 1a: Financial Metrics (from ceo_snapshot view)
SELECT
    SUM(gross_payout) AS gross_payout,
    SUM(chargebacks) AS chargebacks,
    SUM(net_payout) AS net_payout
FROM `payroll-bi-gauntlet.payroll_analytics.ceo_snapshot`
WHERE period_label BETWEEN @start_date AND @end_date
  AND tenant_id = @tenant_id
Query 1b: Distinct Counts Across Range (from stage1_snapshots)
SELECT
    COUNT(DISTINCT business_label) AS total_businesses_ytd,
    COUNT(DISTINCT member_id) AS total_employees_ytd
FROM `payroll-bi-gauntlet.payroll_raw.stage1_snapshots`
WHERE period_label >= @start_date
  AND period_label <= @end_date
  AND tenant_id = @tenant_id
  AND payee_agent_id IN (721995, 668078, 0)
  AND business_label IS NOT NULL
  AND business_label != ''
Query 2: Active Businesses at End Period (from stage1_snapshots)
SELECT
    COUNT(DISTINCT business_label) AS active_businesses,
    COUNT(DISTINCT member_id) AS total_employees
FROM `payroll-bi-gauntlet.payroll_raw.stage1_snapshots`
WHERE period_label = @end_date
  AND tenant_id = @tenant_id
  AND payee_agent_id IN (721995, 668078, 0)
  AND business_label IS NOT NULL
  AND business_label != ''
Date Range Calculation:
  • For period_label = '2025-10-01':
    • start_date = 2025-01-01 (year start)
    • end_date = 2025-10-01 (selected period)
  • Aggregates all data from January through October 2025

QTD Query Pattern

Purpose: Aggregate data from the start of the current quarter to the selected period. Implementation: Uses the same three-query approach as YTD, but with different date range calculation: Date Range Calculation:
  • For period_label = '2025-10-01':
    • start_date = 2025-10-01 (Q4 start, since October is the first month of Q4)
    • end_date = 2025-10-01 (selected period)
  • For period_label = '2025-11-01':
    • start_date = 2025-10-01 (Q4 start)
    • end_date = 2025-11-01 (selected period)
  • Aggregates all data from the start of the quarter through the selected period
SQL Pattern: Same as YTD above, but with different date range:
-- Date range calculation (QTD):
-- start_date = DATE(EXTRACT(YEAR FROM @period_label), (QUARTER - 1) * 3 + 1, 1)
-- end_date = @period_label
SELECT 
    COUNT(DISTINCT business_label) as total_businesses
FROM `payroll-bi-gauntlet.payroll_raw.stage1_snapshots`
WHERE period_label >= (SELECT start_date FROM date_range)
  AND period_label <= (SELECT end_date FROM date_range)
  AND tenant_id = @tenant_id
  AND payee_agent_id IN (721995, 668078, 0)
  AND business_label IS NOT NULL
  AND business_label != ''

Performance Considerations

Why Hybrid Approach?
  • Financial metrics from view: Pre-aggregated values can be summed efficiently, reducing query complexity
  • Distinct counts from raw data: Must query stage1_snapshots directly to get accurate COUNT(DISTINCT ...) across the date range
  • Active businesses query: Separate query for end period ensures we get the correct current state
Optimization Tips:
  • Financial metrics query is fast (pre-aggregated view)
  • Distinct counts query is slower but necessary for accuracy
  • Consider partitioning on period_label for better performance on date range queries
  • Cache results for frequently accessed periods

Month-over-Month Delta Queries

Overview

The dashboard displays month-over-month delta indicators for all KPI cards, showing how metrics compare to previous periods. Delta values are calculated in the backend using _calculate_previous_period_range() helper function.

Previous Period Calculation Logic

Implementation: api/bigquery/queries.py - _calculate_previous_period_range() Month View (MTD):
  • Previous period: Previous month
  • Example: October 2025 → September 2025
  • Comparison type: "month"
QTD View:
  • Previous period: Previous quarter
  • Example: Q4 2025 (Oct-Dec) → Q3 2025 (Jul-Sep)
  • Comparison type: "quarter"
YTD View:
  • Previous period: Previous year YTD (same date range in previous year)
  • Example: Jan-Oct 2025 → Jan-Oct 2024
  • Comparison type: "year"

Delta Field Definitions

All delta fields are optional and may be null/undefined/0:
FieldTypeDescriptionExample
delta_gross_payoutDecimalChange in gross payout+$3,120.00
delta_total_businessesIntegerChange in business count+15
delta_total_employeesIntegerChange in employee count+39
delta_agent_commissionsDecimalChange in agent commissions+$1,157.24
delta_chargebacksDecimalChange in chargebacks-$500.00
previous_period_labelStringHuman-readable previous period”September 2025”
comparison_typeStringType of comparison”month”, “quarter”, or “year”

Query Pattern for Previous Period

Backend Implementation: get_ceo_metrics_range() function
  1. Calculate Previous Period Range:
    prev_start_date, prev_end_date, comparison_type = _calculate_previous_period_range(
        period_label, period_type
    )
    
  2. Query Previous Period Metrics:
    -- Re-use same query logic for previous period
    SELECT
        SUM(gross_payout) AS gross_payout,
        SUM(chargebacks) AS chargebacks,
        COUNT(DISTINCT business_label) AS total_businesses_ytd,
        COUNT(DISTINCT member_id) AS total_employees_ytd
    FROM `payroll-bi-gauntlet.payroll_analytics.ceo_snapshot`
    WHERE period_label BETWEEN @prev_start_date AND @prev_end_date
      AND tenant_id = @tenant_id
    
  3. Calculate Deltas:
    delta_gross_payout = current_gross_payout - previous_gross_payout
    delta_total_businesses = current_total_businesses - previous_total_businesses
    # ... etc
    

Single Month Delta Calculation

For single month periods (period_type=None), the backend leverages existing MoM fields from ceo_snapshot view:
-- ceo_snapshot view already includes MoM fields:
SELECT
    gross_payout_change_mom,
    business_change_mom,
    employee_change_mom
FROM `payroll-bi-gauntlet.payroll_analytics.ceo_snapshot`
WHERE period_label = @period_label
  AND tenant_id = @tenant_id
Fallback Logic: If MoM fields are not available, backend calculates by querying previous month directly.

Edge Case Handling

January (No Previous Month):
  • prev_start_date and prev_end_date return None
  • Delta fields set to 0
  • previous_period_label is None
  • Tooltip not shown, but delta still displayed as 0
Q1 (No Previous Quarter):
  • Previous quarter calculation goes back 3 months
  • If result is before MIN_DATA_DATE (2024-01-01), returns None
  • Same fallback behavior as January
First Year of Data:
  • Previous year YTD calculation results in date before MIN_DATA_DATE
  • Returns None for previous period
  • Delta fields set to 0

Validation Examples (October 2025)

Month View:
  • Current: October 2025
  • Previous: September 2025
  • Expected deltas:
    • delta_gross_payout: +$3,429.06
    • delta_total_businesses: -9
    • delta_total_employees: -253
    • delta_agent_commissions: +$3,251.96
QTD View:
  • Current: Q4 2025 (October only, first month)
  • Previous: Q3 2025 (Jul-Sep)
  • Comparison: Quarter-over-quarter
YTD View:
  • Current: Jan-Oct 2025
  • Previous: Jan-Oct 2024
  • Comparison: Year-over-year

Response Format

The API response includes both aggregated and end-period metrics:
{
  "period_label": "2025-10-01",
  "tenant_id": "creative_benefit_strategies",
  "total_businesses": 221,  // Backward compatibility (maps to total_businesses_ytd)
  "total_businesses_ytd": 221,  // Distinct count across date range
  "active_businesses": 96,  // Distinct count at end period (only for YTD/QTD)
  "total_employees": 1720,  // Employees at end period
  "gross_payout": 195990.48,
  "chargebacks": 0,
  "net_payout": 195990.48,
  "business_owner_commission": 185368.19,
  "total_commission": 10534.29
}

Validation Results (October 2025)

Single Month (period_type=null):
  • total_businesses_ytd = active_businesses = 96 (same period)
QTD (period_type=qtd):
  • For October (first month of Q4): total_businesses_ytd = active_businesses = 96 (equal for first month)
  • For November: total_businesses_ytd > active_businesses (aggregating Oct-Nov)
YTD (period_type=ytd):
  • total_businesses_ytd = 221 (distinct across Jan-Oct)
  • active_businesses = 96 (distinct at October end)
  • total_businesses_ytd > active_businesses (always true for YTD with multiple months AND tenant_id = @tenant_id AND payee_agent_id IN (721995, 668078, 0) AND business_label IS NOT NULL AND business_label != ” GROUP BY tenant_id

**Example**: For `period_label = '2025-10-01'`:
- `start_date` = `2025-10-01` (Q4 start)
- `end_date` = `2025-10-01` (selected period)
- Aggregates Q4 2025 data (October only in this case)

**Quarter Boundaries**:
- Q1: January 1 - March 31
- Q2: April 1 - June 30
- Q3: July 1 - September 30
- Q4: October 1 - December 31

### Key Differences from Single-Period Queries

1. **Date Range Filtering**: Uses `>=` and `<=` instead of `=`
2. **Aggregation Functions**: Uses `SUM()`, `COUNT(DISTINCT ...)` across multiple periods
3. **Table Source**: Queries underlying tables (`stage1_snapshots`, `stage3_snapshots`) instead of views
4. **Performance**: May be slower due to scanning multiple partitions (optimize with partitioned filters)

### Performance Considerations

- **Partitioned Tables**: Ensure `period_label` filters use partitioned columns
- **Clustering**: Use tenant_id clustering for efficient multi-tenant queries
- **Query Timeout**: Set 30s timeout with graceful degradation
- **Caching**: Consider Redis cache for frequently accessed YTD/QTD queries

### 3.4 Handle None Values and Edge Cases
**Common pitfalls:**
```python
# BAD: Will fail if any value is None
total = sum(item.get('value', 0) for item in data)

# GOOD: Handle None explicitly
total = sum(item.get('value', 0) or 0 for item in data)

# GOOD: Safe division
avg = value / max(count, 1)  # Prevents division by zero

3.5 Remove Forced Mock Data

Check for:
# Remove these patterns:
client = None  # Forces mock data
# Force mock data for now
BIGQUERY_AVAILABLE = False
Restore proper initialization:
try:
    client = get_client()
    if client is None:
        logger.warning("BigQuery client not available, using mock data")
    else:
        logger.info(f"BigQuery client initialized for project: {PROJECT_ID}")
except Exception as e:
    logger.error(f"Failed to initialize BigQuery client: {e}")
    client = None

Phase 4: Update Frontend Dashboard

4.1 Review Type Definitions

// Check what fields the API schema expects
// File: dashboard/src/lib/api-client.ts

interface CEOMetrics {
    period_label: string;
    tenant_id: string;
    total_commission: string;  // Note: string, not number!
    total_policies: number;
    total_lives: number;
    total_businesses: number;
    avg_commission_per_policy: string;
    avg_commission_per_life: string;
}

4.2 Update Period Selector for Historical Data

// Add 2024 periods to dropdown
const periods = [
  // 2024 months
  { value: '2024-01-01', label: 'January 2024' },
  { value: '2024-03-01', label: 'March 2024' },
  { value: '2024-06-01', label: 'June 2024' },
  { value: '2024-12-01', label: 'December 2024' },
  // 2025 months
  { value: '2025-01-01', label: 'January 2025' },
  // ... etc
];

4.3 Remove or Flag Hardcoded Fallbacks

// Add feature flag
const USE_FALLBACK = process.env.NEXT_PUBLIC_USE_CEO_FALLBACK === 'true';

if (USE_FALLBACK && selectedPeriod.startsWith('2024-')) {
  console.log('⚠️ Using hardcoded fallback data');
  // Fallback logic here
}

4.4 Fix TypeScript Type Errors

Common issues:
// ERROR: Type 'number' is not assignable to type 'string'
total_commission: fallbackData.total_commission  // ❌

// FIX: Convert to string
total_commission: fallbackData.total_commission.toString()  // ✅

// ERROR: Object literal may only specify known properties
ceoMetricsResponse = {
  ...ceoMetricsResponse,
  total_employees: 100,  // ❌ Not in type definition
  gross_payout: 5000     // ❌ Not in type definition
}

// FIX: Only include defined properties
ceoMetricsResponse = {
  ...ceoMetricsResponse,
  total_businesses: fallbackData.total_businesses,  // ✅
  total_policies: fallbackData.total_employees      // ✅
}

4.5 Update API Client URL

// File: dashboard/src/lib/apiClient.ts
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 
  'https://fastapi-backend-238826317621.us-central1.run.app';

Phase 5: Configure Cloud Run Permissions

5.1 Check Service Account

# Get the service account being used
gcloud run services describe fastapi-backend \
  --region us-central1 \
  --format='value(spec.template.spec.serviceAccountName)'

# Output: fastapi-service-account@payroll-bi-gauntlet.iam.gserviceaccount.com

5.2 Grant BigQuery Permissions

# Required roles for BigQuery access
gcloud projects add-iam-policy-binding payroll-bi-gauntlet \
  --member="serviceAccount:fastapi-service-account@payroll-bi-gauntlet.iam.gserviceaccount.com" \
  --role="roles/bigquery.dataViewer"

gcloud projects add-iam-policy-binding payroll-bi-gauntlet \
  --member="serviceAccount:fastapi-service-account@payroll-bi-gauntlet.iam.gserviceaccount.com" \
  --role="roles/bigquery.user"

5.3 Set Environment Variables

# Deploy with required env vars
gcloud run deploy fastapi-backend \
  --source . \
  --region us-central1 \
  --allow-unauthenticated \
  --set-env-vars GCP_PROJECT_ID=payroll-bi-gauntlet

Phase 6: Deploy and Validate

6.1 Deploy API

# Build and deploy to Cloud Run
gcloud run deploy fastapi-backend \
  --source . \
  --region us-central1 \
  --allow-unauthenticated \
  --set-env-vars GCP_PROJECT_ID=payroll-bi-gauntlet

# Note the service URL from output
# Service URL: https://fastapi-backend-238826317621.us-central1.run.app

6.2 Test API Directly

# Test with a known period
curl "https://fastapi-backend-238826317621.us-central1.run.app/api/v1/ceo-metrics?period_label=2024-03-01"

# Expected response:
# {
#   "period_label": "2024-03-01",
#   "tenant_id": "creative_benefit_strategies",
#   "total_businesses": 33,
#   "total_employees": 484,
#   "gross_payout": 4517.78,
#   "business_owner_commission": 2988.06,
#   ...
# }

6.3 Check API Logs

# Monitor for errors
gcloud run services logs read fastapi-backend --region=us-central1 --limit=20

# Look for:
# ✅ "BigQuery client initialized successfully"
# ✅ "Loaded CEO metrics from BigQuery"
# ❌ "Using mock data" - Should NOT appear
# ❌ "No data found" - Investigate if present

6.4 Deploy Frontend

# Commit changes
git add dashboard/src/pages/ceo/index.tsx dashboard/src/lib/apiClient.ts
git commit -m "fix: Connect CEO dashboard to live BigQuery data"
git push

# Vercel will auto-deploy from GitHub
# Monitor build at: https://vercel.com/dashboard

6.5 Validate Dashboard End-to-End

Manual testing checklist:
  • Dashboard loads without errors
  • Can select 2024 periods
  • Can select 2025 periods
  • Data matches BigQuery direct query results
  • No “No data available” errors
  • Commission calculations are correct
  • No console errors in browser DevTools
Validation queries:
-- Expected results for March 2024
SELECT 
    total_businesses,  -- Should be 33
    total_employees,   -- Should be 484
    gross_payout,      -- Should be ~4517.78
    chargebacks        -- Should be 0
FROM `payroll-bi-gauntlet.payroll_analytics.ceo_snapshot`
WHERE period_label = '2024-03-01';

-- Calculate owner commission manually
SELECT 
    4517.78 - 0 - 1529.72 as owner_commission;  -- Should be 2988.06

Phase 7: Clean Up and Document

7.1 Remove Mock Data Functions (Optional)

# Consider removing or deprecating mock data functions
# Keep them if needed for local development without BigQuery
def get_mock_ceo_metrics(tenant_id: str, period_label: str):
    """DEPRECATED: Only for local dev without BigQuery access"""
    logger.warning("Using mock data - this should only happen in local development")
    # ... mock data ...

7.2 Update API Documentation

# Add docstring with calculation details
def get_ceo_metrics(tenant_id: str, period_label: str):
    """
    Get CEO-level metrics for a specific period from BigQuery.
    
    Calculation Logic:
    - Owner Commission = Gross Payout - Chargebacks - Agent Commissions
    - Agent Commissions = SUM(agent_total) WHERE tpa_applied = TRUE
    
    Data Sources:
    - Base metrics: payroll_analytics.ceo_snapshot view
    - Agent commissions: payroll_processed.stage3_snapshots table
    
    Args:
        tenant_id: Tenant identifier (e.g., 'creative_benefit_strategies')
        period_label: Period in YYYY-MM-01 format (e.g., '2024-03-01')
    
    Returns:
        Dict with CEO metrics including calculated owner commission
    
    Raises:
        Exception: If BigQuery queries fail or no data found
    """

7.3 Create Checkpoint Document

# Save a snapshot of the working state
git tag -a "ceo-dashboard-bigquery-v1" -m "CEO dashboard successfully migrated to BigQuery"
git push origin ceo-dashboard-bigquery-v1

Agent Dashboard Adaptation Guide

Key Differences for Agent Dashboard

AspectCEO DashboardAgent Dashboard (Anticipated)
BigQuery Viewpayroll_analytics.ceo_snapshotpayroll_analytics.agent_performance
Aggregation LevelCompany-wide totalsPer-agent metrics
Key MetricsOwner commission, business countAgent commission, client count, performance rank
Filterspayee_agent_id IN (721995, 668078)payee_agent_id = <specific_agent>
CalculationOwner = Gross - Chargebacks - AgentAgent commission from stage3 directly

Agent View Checklist

- [ ] Create `analytics_agent_performance.sql` view
- [ ] Filter by specific agent ID (from JWT/session)
- [ ] Join with business data for client lists
- [ ] Calculate agent-specific metrics (commission, client retention, etc.)
- [ ] Create `api/routes/agent_metrics.py` endpoint
- [ ] Implement `get_agent_metrics()` function in queries.py
- [ ] Update `dashboard/src/pages/agent/index.tsx`
- [ ] Add agent-specific period filters
- [ ] Grant BigQuery permissions if using different service account
- [ ] Test with multiple agent IDs to ensure data isolation

Troubleshooting Guide

Issue: “Using mock data” in logs

Cause: BigQuery client not initialized or query failing
Fix:
  1. Check GCP_PROJECT_ID environment variable is set
  2. Verify service account has BigQuery permissions
  3. Check for client = None forcing mock mode

Issue: “No data found for period”

Cause: Data not in BigQuery or incorrect date filter
Fix:
  1. Verify data exists: bq query "SELECT * FROM table WHERE period_label = '2024-03-01'"
  2. Check date format (YYYY-MM-01 vs YYYY-MM)
  3. Verify period_label filters in view match data format

Issue: “TypeError: unsupported operand type(s) for +: ‘int’ and ‘NoneType’”

Cause: None values not handled in calculations
Fix:
# Replace
total = value1 + value2

# With
total = (value1 or 0) + (value2 or 0)

Issue: TypeScript build fails with type errors

Cause: Mismatch between API schema and frontend types
Fix:
  1. Check dashboard/src/lib/api-client.ts for type definitions
  2. Ensure all assigned fields exist in type
  3. Convert number to string if schema expects string: .toString()

Issue: Dashboard shows old data after deployment

Cause: Frontend or API not redeployed with latest code
Fix:
  1. Verify latest commit is deployed: check Vercel dashboard
  2. Hard refresh browser: Ctrl+Shift+R (Windows) / Cmd+Shift+R (Mac)
  3. Check API URL points to latest Cloud Run revision

Reference Files

Key Files Modified (CEO Dashboard Example)

Backend:
- main.py (get_ceo_metrics_simple function)
- api/bigquery/queries.py (get_ceo_metrics function)
- integration/bigquery/sql/views/analytics_ceo_snapshot.sql

Frontend:
- dashboard/src/pages/ceo/index.tsx
- dashboard/src/lib/apiClient.ts

Configuration:
- Cloud Run environment variables
- Service account IAM permissions

Canonical Calculation References

  • stage4_outputs/export_excel.py - Lines 210-211 (Owner commission formula)
  • ENHANCED_CHURN_WORKBOOK_CANONICAL_SPEC.md - Business logic documentation

Success Metrics

CEO Dashboard - Validated Results:
  • March 2024: 33 businesses, 484 employees, 4,517.78gross,4,517.78 gross, 2,988.06 owner commission ✅
  • March 2025: 76 businesses, 1,548 employees, 19,522.60gross,19,522.60 gross, 14,111.79 owner commission ✅
Replication Target for Agent Dashboard:
  • Agent-specific data loads correctly for 2024-2025 periods
  • Commission calculations match Excel/Python export logic
  • No mock data warnings in production logs
  • TypeScript builds without errors
  • Dashboard responsive and performant

Next Steps

  1. Agent Dashboard: Follow this playbook to connect agent views to BigQuery
  2. Admin Dashboard: Adapt for system-wide analytics and reporting
  3. Monitoring: Set up alerts for BigQuery query failures or mock data fallbacks
  4. Performance: Consider caching strategies for frequently accessed periods
  5. Testing: Add automated tests to verify calculation logic remains correct

Document Version: 1.0
Last Updated: 2025-10-20
Maintained By: Development Team
Related Documents:
  • BIGQUERY_IMPLEMENTATION_COMPLETE.md
  • ENHANCED_CHURN_WORKBOOK_CANONICAL_SPEC.md
  • DASHBOARD_LOGIC.md