mikeboone Cursor commited on
Commit
5712f77
·
1 Parent(s): cb86ccd

feat: add Spotter Viz as second liveboard creation method

Browse files

Implements the Liveboard Creation Design Doc:
- Settings dropdown: ["HYBRID", "SPOTTER_VIZ"] replaces old 3-option menu
- SPOTTER_VIZ routing: direct REST API + TML, no MCP dependency
- Reuses LiveboardCreator.create_liveboard_tml() + deploy_liveboard()
- Gets outlier patterns from vertical×function system
- Gets model columns from ThoughtSpot model metadata
- Same enhance_mcp_liveboard() post-processing as HYBRID
- Backward compat: old "TML"/"MCP" settings mapped to "HYBRID"
- create_liveboard_from_model() extended with outliers + model_columns params
- CLAUDE.md updated with two-method system documentation

Co-authored-by: Cursor <cursoragent@cursor.com>

CLAUDE.md CHANGED
@@ -194,34 +194,49 @@ When user says "create a test for X":
194
  **Working Code Paths - DO NOT BYPASS THESE:**
195
  ```
196
  Liveboard Creation:
197
- chat_interface.py → create_liveboard_from_model_mcp() → enhance_mcp_liveboard()
 
198
 
199
  DO NOT use create_visualization_tml() directly - that's internal low-level code
200
  ```
201
 
202
  ---
203
 
204
- ## Liveboard Creation
205
 
206
- Liveboard creation is a single unified process with two phases:
 
207
 
208
- 1. **MCP Creation** - Uses ThoughtSpot's AI (via Model Context Protocol at `agent.thoughtspot.app`) to generate smart visualizations from natural language questions
209
- 2. **TML Post-Processing** - Enhances the AI-created liveboard with groups, KPI sparklines, brand colors, and layout refinement
 
 
210
 
211
- These are implemented as separate functions but are **one process** - do NOT treat them as separate "methods" or offer the user a choice between them.
 
 
 
212
 
213
- ### Key Functions (liveboard_creator.py)
214
- - **`create_liveboard_from_model_mcp()`** - Main entry point. Handles MCP creation.
215
- - **`enhance_mcp_liveboard()`** - Post-processing. Exports TML, enhances, re-imports.
216
- - **`LiveboardCreator` class** - TML utilities used during post-processing.
 
 
217
 
218
- ### enhance_mcp_liveboard() Details
219
- 1. Exports the MCP-created liveboard TML
 
220
  2. Classifies visualizations by type (KPI, trend, categorical)
221
- 3. Adds Groups (tabs) to organize by type
222
- 4. Fixes KPI client_state_v2 for sparklines and comparisons
223
- 5. Applies brand colors to groups and tiles
224
- 6. Re-imports the enhanced TML
 
 
 
 
 
225
 
226
  ### KPI Requirements
227
  - **For sparklines and percent change comparisons:**
 
194
  **Working Code Paths - DO NOT BYPASS THESE:**
195
  ```
196
  Liveboard Creation:
197
+ HYBRID: chat_interface.py → create_liveboard_from_model_mcp() → enhance_mcp_liveboard()
198
+ SPOTTER_VIZ: chat_interface.py → create_liveboard_from_model() → enhance_mcp_liveboard()
199
 
200
  DO NOT use create_visualization_tml() directly - that's internal low-level code
201
  ```
202
 
203
  ---
204
 
205
+ ## Liveboard Creation - Two-Method System
206
 
207
+ **Settings UI:** Admin tab "Liveboard Creation Method" dropdown
208
+ **Two options:** HYBRID (default) and SPOTTER_VIZ
209
 
210
+ | Method | Speed | Dependency | Best For |
211
+ |--------|-------|------------|----------|
212
+ | **HYBRID** | ~60-90s | MCP server | AI-driven question selection |
213
+ | **SPOTTER_VIZ** | ~20-30s | Direct REST API only | Production demos, reliability |
214
 
215
+ ### HYBRID Method (MCP + TML Post-Processing)
216
+ 1. MCP creates liveboard via `agent.thoughtspot.app` (bearer auth)
217
+ 2. TML post-processing enhances with groups, KPIs, colors, layout
218
+ - **Entry:** `create_liveboard_from_model_mcp()` → `enhance_mcp_liveboard()`
219
 
220
+ ### SPOTTER_VIZ Method (Direct API + TML)
221
+ 1. LiveboardCreator builds complete TML from outlier patterns + AI
222
+ 2. Deploys via REST API `/metadata/tml/import`
223
+ 3. Same TML post-processing as HYBRID (groups, KPIs, colors, layout)
224
+ - **Entry:** `create_liveboard_from_model()` → `enhance_mcp_liveboard()`
225
+ - No MCP dependency — uses same auth as table/model deployment
226
 
227
+ ### Shared Post-Processing: enhance_mcp_liveboard()
228
+ Both methods share the same post-processing function:
229
+ 1. Exports the liveboard TML
230
  2. Classifies visualizations by type (KPI, trend, categorical)
231
+ 3. Adds Groups with proper `group_layouts` (Golden Demo style)
232
+ 4. KPI group always at top (y=0, full width)
233
+ 5. Fixes KPI sparklines and comparisons
234
+ 6. Applies brand colors (liveboard-level + group-level)
235
+ 7. Re-imports the enhanced TML
236
+
237
+ ### Backward Compatibility
238
+ - Old settings values "TML" or "MCP" are mapped to "HYBRID" in the router
239
+ - `USE_MCP_LIVEBOARD=true` env var still works (maps to HYBRID)
240
 
241
  ### KPI Requirements
242
  - **For sparklines and percent change comparisons:**
chat_interface.py CHANGED
@@ -4133,9 +4133,9 @@ def create_settings_tab():
4133
 
4134
  liveboard_method = gr.Dropdown(
4135
  label="Liveboard Creation Method",
4136
- choices=["HYBRID", "MCP", "TML"],
4137
  value="HYBRID",
4138
- info="HYBRID (recommended): MCP + TML polish. MCP: AI-driven. TML: Template-based."
4139
  )
4140
 
4141
  # Existing Model Section
 
4133
 
4134
  liveboard_method = gr.Dropdown(
4135
  label="Liveboard Creation Method",
4136
+ choices=["HYBRID", "SPOTTER_VIZ"],
4137
  value="HYBRID",
4138
+ info="HYBRID: MCP + TML polish (AI-driven). SPOTTER_VIZ: Direct API + TML (faster, no MCP dependency)."
4139
  )
4140
 
4141
  # Existing Model Section
liveboard_creator.py CHANGED
@@ -2505,12 +2505,15 @@ def create_liveboard_from_model(
2505
  use_case: str,
2506
  num_visualizations: int = 6,
2507
  liveboard_name: str = None,
2508
- llm_model: str = None
 
 
2509
  ) -> Dict:
2510
  """
2511
- Convenience function to create and deploy a Liveboard via TML
2512
 
2513
- This is the pure TML approach - full control over visualizations via YAML templates.
 
2514
 
2515
  Args:
2516
  ts_client: Authenticated ThoughtSpotDeployer instance
@@ -2521,6 +2524,8 @@ def create_liveboard_from_model(
2521
  num_visualizations: Number of visualizations to create
2522
  liveboard_name: Optional custom name for the liveboard
2523
  llm_model: LLM model to use for AI-driven viz generation
 
 
2524
 
2525
  Returns:
2526
  Deployment result dictionary with:
@@ -2529,13 +2534,24 @@ def create_liveboard_from_model(
2529
  - liveboard_name: str
2530
  - liveboard_url: str
2531
  """
2532
- print(f"🚀 Starting TML liveboard creation...", flush=True)
2533
  print(f" - Model: {model_name}", flush=True)
2534
  print(f" - Use case: {use_case}", flush=True)
2535
  print(f" - Visualizations: {num_visualizations}", flush=True)
 
 
 
 
2536
 
2537
  creator = LiveboardCreator(ts_client, model_id, model_name, llm_model)
2538
- liveboard_tml = creator.create_liveboard_tml(company_data, use_case, num_visualizations, liveboard_name)
 
 
 
 
 
 
 
2539
 
2540
  # Deploy and get result
2541
  result = creator.deploy_liveboard(liveboard_tml)
 
2505
  use_case: str,
2506
  num_visualizations: int = 6,
2507
  liveboard_name: str = None,
2508
+ llm_model: str = None,
2509
+ outliers: Optional[List[Dict]] = None,
2510
+ model_columns: Optional[List[Dict]] = None
2511
  ) -> Dict:
2512
  """
2513
+ Create and deploy a Liveboard via TML (Spotter Viz path).
2514
 
2515
+ This is the direct API approach builds complete TML and imports it.
2516
+ Used by both the legacy TML path and the new SPOTTER_VIZ method.
2517
 
2518
  Args:
2519
  ts_client: Authenticated ThoughtSpotDeployer instance
 
2524
  num_visualizations: Number of visualizations to create
2525
  liveboard_name: Optional custom name for the liveboard
2526
  llm_model: LLM model to use for AI-driven viz generation
2527
+ outliers: Optional list of outlier pattern dicts for targeted viz generation
2528
+ model_columns: Optional list of column metadata from TS model
2529
 
2530
  Returns:
2531
  Deployment result dictionary with:
 
2534
  - liveboard_name: str
2535
  - liveboard_url: str
2536
  """
2537
+ print(f"🚀 Starting Spotter Viz liveboard creation...", flush=True)
2538
  print(f" - Model: {model_name}", flush=True)
2539
  print(f" - Use case: {use_case}", flush=True)
2540
  print(f" - Visualizations: {num_visualizations}", flush=True)
2541
+ if outliers:
2542
+ print(f" - Outlier patterns: {len(outliers)}", flush=True)
2543
+ if model_columns:
2544
+ print(f" - Model columns: {len(model_columns)}", flush=True)
2545
 
2546
  creator = LiveboardCreator(ts_client, model_id, model_name, llm_model)
2547
+
2548
+ # If external model_columns provided, override the auto-fetched ones
2549
+ if model_columns:
2550
+ creator.model_columns = model_columns
2551
+
2552
+ liveboard_tml = creator.create_liveboard_tml(
2553
+ company_data, use_case, num_visualizations, liveboard_name, outliers
2554
+ )
2555
 
2556
  # Deploy and get result
2557
  result = creator.deploy_liveboard(liveboard_tml)
sprint_2026_02.md CHANGED
@@ -89,6 +89,16 @@
89
  ### Done
90
 
91
 
 
 
 
 
 
 
 
 
 
 
92
  #### Feb 6, 2026 - Gradio Compat + Hybrid Liveboard Layout Fix
93
  - [x] **Gradio 4.44.1 + pydantic 2.12 + httpx 0.28 compatibility** ✅
94
  - Monkey-patch `url_ok()` for localhost check bypass
 
89
  ### Done
90
 
91
 
92
+ #### Feb 6, 2026 - Spotter Viz Method Added (Design Doc Implementation)
93
+ - [x] **Settings dropdown updated**: `["HYBRID", "SPOTTER_VIZ"]` replaces old 3-option dropdown ✅
94
+ - [x] **SPOTTER_VIZ routing in deployer**: Direct TML path with outliers + model_columns ✅
95
+ - [x] **Backward compatibility**: old "TML"/"MCP" values mapped to "HYBRID" ✅
96
+ - [x] **Shared post-processing**: Both methods use `enhance_mcp_liveboard()` ✅
97
+ - [x] **create_liveboard_from_model() extended**: accepts `outliers` and `model_columns` params ✅
98
+ - [x] **CLAUDE.md updated**: Two-method system documented ✅
99
+ - [ ] **To test**: Run SPOTTER_VIZ end-to-end with Retail Sales
100
+
101
+
102
  #### Feb 6, 2026 - Gradio Compat + Hybrid Liveboard Layout Fix
103
  - [x] **Gradio 4.44.1 + pydantic 2.12 + httpx 0.28 compatibility** ✅
104
  - Monkey-patch `url_ok()` for localhost check bypass
thoughtspot_deployer.py CHANGED
@@ -2123,9 +2123,13 @@ class ThoughtSpotDeployer:
2123
  use_mcp = os.getenv('USE_MCP_LIVEBOARD', 'false').lower() == 'true'
2124
  method = 'MCP' if use_mcp else 'HYBRID'
2125
 
2126
- # Normalize method name
2127
  method = method.upper()
2128
- if method not in ['TML', 'MCP', 'HYBRID']:
 
 
 
 
2129
  log_progress(f"[WARN] Unknown liveboard method '{method}', defaulting to HYBRID")
2130
  method = 'HYBRID'
2131
 
@@ -2140,22 +2144,93 @@ class ThoughtSpotDeployer:
2140
  'use_case': use_case or 'General Analytics'
2141
  }
2142
 
2143
- if method == 'TML':
2144
- # Pure TML approach - full control via YAML templates
2145
- from liveboard_creator import create_liveboard_from_model
 
 
 
 
 
 
 
 
 
2146
 
2147
- liveboard_result = create_liveboard_from_model(
2148
- ts_client=self,
2149
- model_id=model_guid,
2150
- model_name=model_name,
2151
- company_data=company_data,
2152
- use_case=use_case or 'General Analytics',
2153
- num_visualizations=8,
2154
- liveboard_name=liveboard_name,
2155
- llm_model=llm_model
2156
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2157
 
2158
- elif method in ('MCP', 'HYBRID'):
2159
  # MCP and HYBRID both use MCP for creation
2160
  # HYBRID adds TML post-processing enhancement
2161
  from liveboard_creator import create_liveboard_from_model_mcp, enhance_mcp_liveboard
 
2123
  use_mcp = os.getenv('USE_MCP_LIVEBOARD', 'false').lower() == 'true'
2124
  method = 'MCP' if use_mcp else 'HYBRID'
2125
 
2126
+ # Normalize method name and backward compatibility
2127
  method = method.upper()
2128
+ # Map old values to current options
2129
+ if method in ('TML', 'MCP'):
2130
+ log_progress(f"[INFO] Mapping legacy method '{method}' → HYBRID")
2131
+ method = 'HYBRID'
2132
+ if method not in ['HYBRID', 'SPOTTER_VIZ']:
2133
  log_progress(f"[WARN] Unknown liveboard method '{method}', defaulting to HYBRID")
2134
  method = 'HYBRID'
2135
 
 
2144
  'use_case': use_case or 'General Analytics'
2145
  }
2146
 
2147
+ if method == 'SPOTTER_VIZ':
2148
+ # Spotter Viz: Direct REST API + TML no MCP dependency
2149
+ from liveboard_creator import create_liveboard_from_model, enhance_mcp_liveboard
2150
+
2151
+ # Get actual column names from ThoughtSpot model
2152
+ model_columns = self.get_model_columns(model_guid)
2153
+ if not model_columns:
2154
+ log_progress(f" ⚠️ Could not get model columns, falling back to DDL")
2155
+ model_columns = []
2156
+ for table_name, columns_list in tables.items():
2157
+ for col in columns_list:
2158
+ model_columns.append(col)
2159
 
2160
+ # Get outlier patterns from the vertical×function system
2161
+ outlier_dicts = []
2162
+ try:
2163
+ from outlier_system import get_outliers_for_use_case
2164
+ from demo_personas import parse_use_case
2165
+ uc_vertical, uc_function = parse_use_case(use_case or '')
2166
+ if uc_vertical or uc_function:
2167
+ outlier_config = get_outliers_for_use_case(
2168
+ uc_vertical or "Generic",
2169
+ uc_function or "Generic"
2170
+ )
2171
+ for op in outlier_config.required:
2172
+ outlier_dicts.append({
2173
+ 'title': op.name,
2174
+ 'insight': op.viz_talking_point,
2175
+ 'viz_type': op.viz_type,
2176
+ 'show_me_query': op.viz_question,
2177
+ 'kpi_companion': True,
2178
+ 'spotter_questions': op.spotter_questions,
2179
+ })
2180
+ for op in outlier_config.optional[:2]:
2181
+ outlier_dicts.append({
2182
+ 'title': op.name,
2183
+ 'insight': op.viz_talking_point,
2184
+ 'viz_type': op.viz_type,
2185
+ 'show_me_query': op.viz_question,
2186
+ 'kpi_companion': False,
2187
+ 'spotter_questions': op.spotter_questions,
2188
+ })
2189
+ if outlier_dicts:
2190
+ log_progress(f" [SPOTTER] Using {len(outlier_dicts)} outlier patterns from {uc_vertical}×{uc_function}")
2191
+ except Exception as outlier_err:
2192
+ log_progress(f" [SPOTTER] Outlier loading skipped: {outlier_err}")
2193
+
2194
+ log_progress(f" [SPOTTER] Model: {model_name}, GUID: {model_guid}")
2195
+ log_progress(f" [SPOTTER] Using {len(model_columns)} columns from ThoughtSpot model")
2196
+ log_progress(f" Step 1/2: Building liveboard via TML...")
2197
+
2198
+ try:
2199
+ liveboard_result = create_liveboard_from_model(
2200
+ ts_client=self,
2201
+ model_id=model_guid,
2202
+ model_name=model_name,
2203
+ company_data=company_data,
2204
+ use_case=use_case or 'General Analytics',
2205
+ num_visualizations=8,
2206
+ liveboard_name=liveboard_name,
2207
+ llm_model=llm_model,
2208
+ outliers=outlier_dicts if outlier_dicts else None,
2209
+ model_columns=model_columns
2210
+ )
2211
+ except Exception as spotter_error:
2212
+ import traceback
2213
+ error_trace = traceback.format_exc()
2214
+ log_progress(f" [SPOTTER ERROR] {type(spotter_error).__name__}: {str(spotter_error)}")
2215
+ liveboard_result = {'success': False, 'error': str(spotter_error), 'traceback': error_trace}
2216
+
2217
+ # Spotter Viz: Add TML enhancement (same as Hybrid post-processing)
2218
+ if liveboard_result.get('success') and liveboard_result.get('liveboard_guid'):
2219
+ log_progress(f" Step 2/2: Enhancing with TML post-processing...")
2220
+ enhance_result = enhance_mcp_liveboard(
2221
+ liveboard_guid=liveboard_result['liveboard_guid'],
2222
+ company_data=company_data,
2223
+ ts_client=self,
2224
+ add_groups=True,
2225
+ fix_kpis=True,
2226
+ apply_brand_colors=True
2227
+ )
2228
+ if enhance_result.get('success'):
2229
+ log_progress(f" [OK] Enhancement applied: {', '.join(enhance_result.get('enhancements', []))}")
2230
+ else:
2231
+ log_progress(f" [WARN] Enhancement partial: {enhance_result.get('message', '')[:80]}")
2232
 
2233
+ elif method == 'HYBRID':
2234
  # MCP and HYBRID both use MCP for creation
2235
  # HYBRID adds TML post-processing enhancement
2236
  from liveboard_creator import create_liveboard_from_model_mcp, enhance_mcp_liveboard