mikeboone Claude Opus 4.8 commited on
Commit
3d735c7
·
1 Parent(s): 1907a8d

fix(mcp+pipeline): share demos with operator + harden data-quality consumers

Browse files

- mcp_server: every MCP build shares the created model + liveboard with the operator
(MCP_SHARE_WITH, default mike.boone@thoughtspot.com) so demos are visible no matter
who ran them. Owner unchanged — the build still authenticates AS owner_email, so the
caller passed in owns the objects.
- blueprint.from_dict: drop non-numeric seasonal `monthly` params too (line 261) — the
merged author can emit a dict there like it did for params.
- engine + validator: _safe_date() tolerates a non-ISO insight window bound (e.g. the
LLM token 'relative_minus_8_months') instead of crashing on date.fromisoformat — the
second merge bug that failed Cloudscape.

Root theme: the merged blueprint author emits richer output than the parser/engine
expected; harden the consumers to tolerate it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

demoprep_app/dataset/engine.py CHANGED
@@ -31,6 +31,18 @@ from demoprep_app.scenario.blueprint import (
31
  from demoprep_app.scenario.contract import DimensionSpec, ScenarioContract
32
 
33
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  class BlueprintEngine:
35
  """Deterministic executor for DemoBlueprint -> DatasetBundle."""
36
 
@@ -276,9 +288,11 @@ def _insight_multiplier(entries, picked: dict[str, str], d: date) -> float:
276
  for i, effmult in entries:
277
  if i.insight_type != "seasonal_spike" and picked.get(i.dimension) != i.dimension_value:
278
  continue
279
- if i.window_start and d < date.fromisoformat(i.window_start):
 
280
  continue
281
- if i.window_end and d > date.fromisoformat(i.window_end):
 
282
  continue
283
  mult *= effmult
284
  return mult
 
31
  from demoprep_app.scenario.contract import DimensionSpec, ScenarioContract
32
 
33
 
34
+ def _safe_date(value):
35
+ """Parse an ISO date; return None if missing or not a valid ISO string (e.g. an
36
+ LLM-invented relative token like 'relative_minus_8_months'), so a bad insight
37
+ window bound is skipped rather than crashing dataset generation."""
38
+ if not value:
39
+ return None
40
+ try:
41
+ return date.fromisoformat(value)
42
+ except (ValueError, TypeError):
43
+ return None
44
+
45
+
46
  class BlueprintEngine:
47
  """Deterministic executor for DemoBlueprint -> DatasetBundle."""
48
 
 
288
  for i, effmult in entries:
289
  if i.insight_type != "seasonal_spike" and picked.get(i.dimension) != i.dimension_value:
290
  continue
291
+ _ws = _safe_date(i.window_start)
292
+ if _ws and d < _ws:
293
  continue
294
+ _we = _safe_date(i.window_end)
295
+ if _we and d > _we:
296
  continue
297
  mult *= effmult
298
  return mult
demoprep_app/dataset/validator.py CHANGED
@@ -145,6 +145,18 @@ def _row_date(row: dict[str, Any]) -> date | None:
145
  return None
146
 
147
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  def _check_insight(
149
  blueprint: DemoBlueprint,
150
  insight: PlantedInsight,
@@ -155,8 +167,8 @@ def _check_insight(
155
  laggard = promised < 1.0
156
  required = 1.0 + (promised - 1.0) * MIN_REALIZED_FRACTION # works for both directions
157
 
158
- start = date.fromisoformat(insight.window_start) if insight.window_start else None
159
- end = date.fromisoformat(insight.window_end) if insight.window_end else None
160
 
161
  def in_window(d: date | None) -> bool:
162
  if d is None:
 
145
  return None
146
 
147
 
148
+ def _safe_date(value):
149
+ """Parse an ISO date; return None if missing or not a valid ISO string (e.g. an
150
+ LLM-invented relative token), so a bad insight window bound is ignored rather than
151
+ crashing validation."""
152
+ if not value:
153
+ return None
154
+ try:
155
+ return date.fromisoformat(value)
156
+ except (ValueError, TypeError):
157
+ return None
158
+
159
+
160
  def _check_insight(
161
  blueprint: DemoBlueprint,
162
  insight: PlantedInsight,
 
167
  laggard = promised < 1.0
168
  required = 1.0 + (promised - 1.0) * MIN_REALIZED_FRACTION # works for both directions
169
 
170
+ start = _safe_date(insight.window_start)
171
+ end = _safe_date(insight.window_end)
172
 
173
  def in_window(d: date | None) -> bool:
174
  if d is None:
demoprep_app/scenario/blueprint.py CHANGED
@@ -259,7 +259,8 @@ class DemoBlueprint:
259
 
260
  seas_raw = raw.get("seasonality") or {}
261
  seasonality = Seasonality(
262
- monthly={int(k): float(v) for k, v in (seas_raw.get("monthly") or {}).items()},
 
263
  trend_pct_per_year=float(seas_raw.get("trend_pct_per_year", 6.0) or 0.0),
264
  narrative=[str(s) for s in (seas_raw.get("narrative") or [])],
265
  )
 
259
 
260
  seas_raw = raw.get("seasonality") or {}
261
  seasonality = Seasonality(
262
+ monthly={int(k): float(v) for k, v in (seas_raw.get("monthly") or {}).items()
263
+ if str(k).lstrip("-").isdigit() and isinstance(v, (int, float)) and not isinstance(v, bool)},
264
  trend_pct_per_year=float(seas_raw.get("trend_pct_per_year", 6.0) or 0.0),
265
  narrative=[str(s) for s in (seas_raw.get("narrative") or [])],
266
  )
mcp_server.py CHANGED
@@ -366,6 +366,11 @@ def _run_build(run_id: str, brief: str, company_name: str, use_case: str, compan
366
  controller.settings["model"] = controller.settings.get("model") or DEFAULT_LLM_MODEL
367
  controller.settings["thoughtspot_url"] = ts_target_url
368
  controller.settings["thoughtspot_trusted_auth_key"] = ts_auth_key
 
 
 
 
 
369
 
370
  # vertical / function / use_case_config exactly as the runner does
371
  controller.vertical, controller.function = parse_use_case(use_case or "")
 
366
  controller.settings["model"] = controller.settings.get("model") or DEFAULT_LLM_MODEL
367
  controller.settings["thoughtspot_url"] = ts_target_url
368
  controller.settings["thoughtspot_trusted_auth_key"] = ts_auth_key
369
+ # Always share the created model + liveboard with the operator, so every demo is
370
+ # visible no matter who ran it. The OWNER is unchanged — whoever was passed as
371
+ # owner_email (the build authenticates AS that user, so they own the objects);
372
+ # this just adds the operator as a viewer. Override target via MCP_SHARE_WITH.
373
+ controller.settings["share_with"] = os.getenv("MCP_SHARE_WITH", "mike.boone@thoughtspot.com")
374
 
375
  # vertical / function / use_case_config exactly as the runner does
376
  controller.vertical, controller.function = parse_use_case(use_case or "")