/*!
 * BlockWriteAI AI Sheet Analyzer Plugin
 * Premium spreadsheet metadata analyzer with backend AI and credit accounting.
 */
(function (global) {
  "use strict";

  var BlockWriteAI = global.BlockWriteAI;
  if (!BlockWriteAI || BlockWriteAI.__sheetAnalyzerInstalled) return;
  BlockWriteAI.__sheetAnalyzerInstalled = true;

  var H = BlockWriteAI.helpers || {};
  var el = H.el || function (tag, className, attrs) {
    var node = document.createElement(tag);
    if (className) node.className = className;
    Object.keys(attrs || {}).forEach(function (key) {
      if (key === "html") node.innerHTML = attrs[key];
      else if (key === "text") node.textContent = attrs[key];
      else if (key === "dataset") Object.keys(attrs[key] || {}).forEach(function (dataKey) { node.dataset[dataKey] = attrs[key][dataKey]; });
      else if (attrs[key] !== false && attrs[key] != null) node.setAttribute(key, attrs[key] === true ? "" : attrs[key]);
    });
    return node;
  };
  var esc = H.escapeHTML || function (value) {
    return String(value == null ? "" : value).replace(/[&<>"]/g, function (ch) {
      return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[ch];
    });
  };
  var sanitize = H.sanitizeHTML || function (value) { return String(value || ""); };
  var svgIcon = H.svgIcon || function (name, label) {
    return '<svg class="rbe-svg-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" data-rbe-icon="' + esc(name || "circle") + '"><circle cx="12" cy="12" r="8"/></svg><span class="rbe-sr-only">' + esc(label || name) + "</span>";
  };

  var MAX_FILE_SIZE = 10 * 1024 * 1024;
  var ANALYSIS_CHUNK_SIZE = 10000;
  var CHART_TYPES = ["bar", "line", "pie", "doughnut", "area", "scatter", "stackedBar", "comparison", "trend", "kpi"];
  var DRAFT_PREFIX = "blockwriteai:sheetAnalyzer:draft:";
  var DRAFT_LATEST = "blockwriteai:sheetAnalyzer:draft:latest";

  function storageScope() {
    try {
      return String(global.location && global.location.pathname || "document").replace(/[^a-z0-9._/-]+/gi, "_");
    } catch (error) {
      return "document";
    }
  }

  function storageKeyFor(data) {
    data = data || {};
    if (data.draftId) return DRAFT_PREFIX + storageScope() + ":block:" + String(data.draftId);
    var meta = data.metadata || {};
    var identity = data.fileId || [
      meta.fileName || "",
      meta.fileSize || "",
      meta.rowCount || "",
      meta.columnCount || ""
    ].join(":");
    identity = identity || "latest";
    return DRAFT_PREFIX + storageScope() + ":" + identity;
  }

  function legacyStorageKeyFor(data) {
    data = data || {};
    var draftId = data.draftId;
    data.draftId = "";
    var key = storageKeyFor(data);
    data.draftId = draftId;
    return key;
  }

  function readDraft(data) {
    try {
      if (!global.localStorage) return null;
      data = data || {};
      var direct = global.localStorage.getItem(storageKeyFor(data));
      var legacy = data.metadata || data.fileId ? global.localStorage.getItem(legacyStorageKeyFor(data)) : null;
      var draft = JSON.parse(direct || legacy || "null");
      return draft && draft.state ? hydrateDraftState(draft.state) : null;
    } catch (error) {
      return null;
    }
  }

  function hydrateDraftState(state) {
    if (!state || !state.metadata || state.metadata._localRows || !Array.isArray(state.metadata.draftRows)) return state;
    try {
      Object.defineProperty(state.metadata, "_localRows", {
        value: state.metadata.draftRows,
        enumerable: false,
        configurable: true
      });
    } catch (error) {}
    return state;
  }

  function meaningfulNested(blocks) {
    return Array.isArray(blocks) ? blocks.filter(function (block) {
      return block && block.type && String((block.data || {}).text || "").trim();
    }) : [];
  }

  function draftScore(state) {
    if (!state) return 0;
    var score = 0;
    if (state.metadata) score += 4;
    if (state.fileId) score += 2;
    if (state.analysis) score += 6;
    if (state.status === "analyzed") score += 3;
    if (state.status === "uploaded") score += 1;
    score += Math.min(meaningfulNested(state.nestedBlocks).length, 6);
    return score;
  }

  function mergeDraftData(data, draft) {
    data = data || {};
    if (!draft || !draftScore(draft)) return data;
    var merged = Object.assign({}, draft, data);
    if (data.metadata && draft.metadata && !data.metadata._localRows && !Array.isArray(data.metadata.draftRows) && Array.isArray(draft.metadata.draftRows)) {
      merged.metadata = Object.assign({}, data.metadata, { draftRows: draft.metadata.draftRows });
      hydrateDraftState(merged);
    }
    if (!data.metadata && draft.metadata) merged.metadata = draft.metadata;
    if (!data.fileId && draft.fileId) merged.fileId = draft.fileId;
    if (!data.analysis && draft.analysis) merged.analysis = draft.analysis;
    if (!(Array.isArray(data.blocks) && data.blocks.length) && Array.isArray(draft.blocks)) merged.blocks = draft.blocks;
    if (!meaningfulNested(data.nestedBlocks).length && Array.isArray(draft.nestedBlocks)) merged.nestedBlocks = draft.nestedBlocks;
    if ((!data.status || data.status === "empty") && draft.status) merged.status = draft.status;
    if (draft.status === "analyzed" && !data.analysis && draft.analysis) merged.status = "analyzed";
    return merged;
  }

  function hasUsableSheetRows(metadata) {
    return !!(metadata && ((Array.isArray(metadata._localRows) && metadata._localRows.length) || (Array.isArray(metadata.draftRows) && metadata.draftRows.length)));
  }

  function hasSheetState(state) {
    return !!(state && (state.metadata || state.fileId || state.analysis || state.status === "analyzed"));
  }

  function hasNestedContent(blocks) {
    return Array.isArray(blocks) && blocks.some(function (block) {
      if (!block || !block.type) return false;
      var data = block.data || {};
      if (block.type !== "paragraph") return true;
      return !!String(data.text || "").replace(/<[^>]+>/g, "").trim();
    });
  }

  function isSheetPreviewRender(editor) {
    var options = editor && editor.options || {};
    var holder = editor && editor.holder;
    if (options.htmlPreviewMode || options.previewRuntime) return true;
    if (holder && holder.classList && (holder.classList.contains("rbe-preview-renderer") || holder.classList.contains("rbe-export-renderer"))) return true;
    return false;
  }

  function writeDraft(state) {
    try {
      if (!global.localStorage || !state) return;
      if (!hasSheetState(state)) return;
      var score = draftScore(state);
      if (!score) return;
      var existing = readDraft(state);
      if (existing && draftScore(existing) > score && !state.metadata && !state.analysis) return;
      var payload = JSON.stringify({ updatedAt: Date.now(), state: serializableDraftState(state) });
      global.localStorage.setItem(storageKeyFor(state), payload);
      global.localStorage.setItem(DRAFT_LATEST + ":" + storageScope(), payload);
    } catch (error) {}
  }

  function clearDraft(state) {
    try {
      if (!global.localStorage || !state) return;
      var key = storageKeyFor(state);
      var latestKey = DRAFT_LATEST + ":" + storageScope();
      var latest = global.localStorage.getItem(latestKey);
      var current = global.localStorage.getItem(key);
      global.localStorage.removeItem(key);
      if (latest && current && latest === current) global.localStorage.removeItem(latestKey);
    } catch (error) {}
  }

  function serializableDraftState(state) {
    var draft = Object.assign({}, state || {});
    if (state && state.metadata) {
      draft.metadata = Object.assign({}, state.metadata);
      if (!Array.isArray(draft.metadata.draftRows)) {
        draft.metadata.draftRows = (state.metadata._localRows || state.metadata.draftRows || []).slice(0, 10000);
      }
    }
    return draft;
  }

  function clearDraftsForScope() {
    try {
      if (!global.localStorage) return;
      var scope = storageScope();
      var prefix = DRAFT_PREFIX + scope + ":";
      for (var index = global.localStorage.length - 1; index >= 0; index -= 1) {
        var key = global.localStorage.key(index);
        if (key && (key.indexOf(prefix) === 0 || key === DRAFT_LATEST + ":" + scope)) {
          global.localStorage.removeItem(key);
        }
      }
    } catch (error) {}
  }

  document.addEventListener("click", function (event) {
    var target = event.target && event.target.closest && event.target.closest("[data-rbe-clear='true']");
    if (target) clearDraftsForScope();
  }, true);

  function injectStyles() {
    if (document.querySelector("style[data-blockwriteai-sheet-analyzer]")) return;
    var style = document.createElement("style");
    style.dataset.blockwriteaiSheetAnalyzer = "true";
    style.textContent =
      ".rbe-sheet-card{display:grid;gap:12px;border:1px solid var(--rbe-border,#d7dee8);border-radius:8px;background:var(--rbe-surface,#fff);color:var(--rbe-text,#172033);padding:12px;}" +
      ".rbe-sheet-head{display:flex;justify-content:space-between;gap:10px;align-items:center;}" +
      ".rbe-sheet-title{display:flex;align-items:center;gap:9px;font-weight:850;color:var(--rbe-text,#172033);}" +
      ".rbe-sheet-title .rbe-svg-icon{color:#1d4ed8;}" +
      ".rbe-sheet-badges{display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end;}" +
      ".rbe-sheet-badge{display:inline-flex;align-items:center;gap:5px;border:1px solid #bfdbfe;border-radius:999px;background:#eff6ff;color:#1d4ed8;padding:4px 8px;font-size:12px;font-weight:800;}" +
      ".rbe-sheet-badge.is-premium{border-color:#fde68a;background:#fffbeb;color:#92400e;}" +
      ".rbe-sheet-drop{position:relative;display:grid;place-items:center;gap:9px;min-height:174px;border:2px dashed #7c3cff;border-radius:14px;background:#fbf8ff;text-align:center;padding:22px;cursor:pointer;transition:border-color .16s ease,background .16s ease;}" +
      ".rbe-sheet-drop.is-dragging{border-color:#2563eb;background:#f3f7ff;}" +
      ".rbe-sheet-drop input{position:absolute;inset:0;opacity:0;cursor:pointer;}" +
      ".rbe-sheet-drop .rbe-svg-icon{width:38px;height:38px;color:#1d4ed8;stroke-width:2.8;}" +
      ".rbe-sheet-drop strong{font-size:15px;color:#273348;}" +
      ".rbe-sheet-drop span{font-size:14px;color:#475467;}" +
      ".rbe-sheet-drop small{font-size:12px;color:#7b8aa5;}" +
      ".rbe-sheet-meta{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;}" +
      ".rbe-sheet-meta div{border:1px solid var(--rbe-border,#d7dee8);border-radius:7px;background:var(--rbe-surface-soft,#f8fafc);padding:8px;}" +
      ".rbe-sheet-meta span{display:block;font-size:11px;font-weight:850;color:var(--rbe-muted,#667085);text-transform:uppercase;}" +
      ".rbe-sheet-meta strong{display:block;margin-top:4px;font-size:15px;color:var(--rbe-text,#172033);overflow-wrap:anywhere;}" +
      ".rbe-sheet-columns{display:grid;max-height:168px;overflow:auto;border:1px solid var(--rbe-border,#d7dee8);border-radius:7px;background:var(--rbe-surface-soft,#f8fafc);}" +
      ".rbe-sheet-column{display:grid;grid-template-columns:minmax(0,1fr) 100px 110px;gap:8px;align-items:center;font-size:12px;padding:5px 8px;}" +
      ".rbe-sheet-column.is-head{position:sticky;top:0;background:#e9eff7;color:#172033;font-weight:900;border-bottom:1px solid var(--rbe-border,#d7dee8);z-index:1;}" +
      ".rbe-sheet-column.has-missing{background:#fff1f2;border-left:3px solid #f87171;}" +
      ".rbe-sheet-column.has-missing strong,.rbe-sheet-column.has-missing span{color:#991b1b;font-weight:900;}" +
      ".rbe-sheet-column strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}" +
      ".rbe-sheet-column span{color:var(--rbe-muted,#667085);font-weight:750;text-align:right;}" +
      ".rbe-sheet-column.is-head span{color:#172033;}" +
      ".rbe-sheet-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center;}" +
      ".rbe-sheet-actions button{border:0;border-radius:6px;background:transparent;color:var(--rbe-text,#172033);padding:6px;font-weight:800;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:0;min-width:32px;min-height:32px;}" +
      ".rbe-sheet-actions button[data-primary]{background:transparent;color:#2563eb;}" +
      ".rbe-sheet-actions button:hover{background:var(--rbe-surface-soft,#f1f5f9);}" +
      ".rbe-sheet-actions button span:not(.rbe-sr-only){display:none;}" +
      ".rbe-sheet-actions button:disabled{opacity:.55;cursor:not-allowed;}" +
      ".rbe-sheet-insert,.rbe-sheet-nested-canvas .rbe-block-add{display:none!important;}" +
      ".rbe-sheet-meta[hidden],.rbe-sheet-columns[hidden],.rbe-sheet-error[hidden],.rbe-sheet-result[hidden],.rbe-sheet-actions button[hidden]{display:none!important;}" +
      ".rbe-sheet-toggle{display:inline-flex;align-items:center;gap:7px;font-size:13px;font-weight:800;color:var(--rbe-text,#172033);}" +
      ".rbe-sheet-toggle.is-disabled{opacity:.58;cursor:not-allowed;}" +
      ".rbe-sheet-toggle input{width:16px;height:16px;}" +
      ".rbe-sheet-toggle input:disabled{cursor:not-allowed;}" +
      ".rbe-sheet-error{border:1px solid #fecaca;border-radius:7px;background:#fff1f2;color:#991b1b;padding:8px 10px;font-size:13px;}" +
      ".rbe-sheet-result{display:grid;gap:9px;border:1px solid var(--rbe-border,#d7dee8);border-radius:7px;background:var(--rbe-surface-soft,#f8fafc);padding:10px;}" +
      ".rbe-sheet-result h4{margin:0;font-size:14px;color:var(--rbe-text,#172033);}" +
      ".rbe-sheet-result ul{margin:0;padding-left:18px;color:var(--rbe-text,#172033);}" +
      ".rbe-sheet-live-lines{list-style:none!important;margin:0!important;padding:0!important;display:grid;gap:7px;}" +
      ".rbe-sheet-live-lines li{display:flex;align-items:flex-start;gap:8px;color:var(--rbe-text,#172033);}" +
      ".rbe-sheet-live-check{position:relative;flex:0 0 16px;width:16px;height:16px;margin-top:2px;border:2px solid #94a3b8;border-radius:999px;background:#fff;}" +
      ".rbe-sheet-live-lines li.is-complete .rbe-sheet-live-check{border-color:#16a34a;background:#16a34a;}" +
      ".rbe-sheet-live-lines li.is-complete .rbe-sheet-live-check:after{content:'';position:absolute;left:4px;top:1px;width:4px;height:8px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg);}" +
      ".rbe-sheet-live-lines li.is-error{color:#991b1b;}" +
      ".rbe-sheet-live-lines li.is-error .rbe-sheet-live-check{border-color:#dc2626;background:#dc2626;}" +
      ".rbe-sheet-live-lines li.is-error .rbe-sheet-live-check:before,.rbe-sheet-live-lines li.is-error .rbe-sheet-live-check:after{content:'';position:absolute;left:3px;top:6px;width:8px;height:2px;background:#fff;border:0;transform:rotate(45deg);}" +
      ".rbe-sheet-live-lines li.is-error .rbe-sheet-live-check:after{transform:rotate(-45deg);}" +
      ".rbe-sheet-nested{display:grid;gap:8px;border-top:1px solid var(--rbe-border,#d7dee8);padding-top:10px;}" +
      ".rbe-sheet-nested-head{display:flex;align-items:center;justify-content:space-between;gap:8px;}" +
      ".rbe-sheet-nested-head strong{font-size:13px;color:var(--rbe-text,#172033);}" +
      ".rbe-sheet-nested-canvas{display:grid;gap:8px;min-height:54px;border:1px dashed var(--rbe-border,#d7dee8);border-radius:7px;padding:8px;background:var(--rbe-surface-soft,#f8fafc);}" +
      ".rbe-sheet-manual{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:start;}" +
      ".rbe-sheet-manual[hidden]{display:none!important;}" +
      ".rbe-sheet-manual-field{min-width:0;display:grid;gap:6px;}" +
      ".rbe-sheet-manual-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center;}" +
      ".rbe-sheet-manual-input{width:100%;min-height:38px;border:1px solid var(--rbe-border,#d7dee8);border-radius:7px;background:var(--rbe-surface,#fff);color:var(--rbe-text,#172033);padding:8px 10px;font:inherit;font-size:13px;}" +
      ".rbe-sheet-manual-credit{min-height:38px;display:inline-flex;align-items:center;justify-content:center;white-space:nowrap;border:1px solid #bfdbfe;border-radius:999px;background:#eff6ff;color:#1d4ed8;font-size:12px;font-weight:850;padding:0 10px;}" +
      ".rbe-sheet-manual-credit[hidden],.rbe-sheet-suggest[hidden],.rbe-sheet-prompt-lint[hidden]{display:none!important;}" +
      ".rbe-sheet-prompt-lint{display:flex;flex-wrap:wrap;gap:6px;align-items:center;}" +
      ".rbe-sheet-lint-chip{border:1px solid var(--rbe-lint-border,#fde68a);background:var(--rbe-lint-bg,#fffbeb);color:var(--rbe-lint-text,#92400e);border-radius:999px;padding:4px 8px;font-size:11px;font-weight:850;line-height:1.25;cursor:help;}" +
      ".rbe-sheet-lint-chip[data-severity='error']{--rbe-lint-border:#fecaca;--rbe-lint-bg:#fef2f2;--rbe-lint-text:#b91c1c;}" +
      ".rbe-sheet-lint-chip[data-severity='info']{--rbe-lint-border:#bfdbfe;--rbe-lint-bg:#eff6ff;--rbe-lint-text:#1d4ed8;}" +
      ".rbe-sheet-suggest{display:grid;gap:7px;max-height:230px;overflow:auto;border:1px solid rgba(148,163,184,.38);border-radius:10px;background:rgba(255,255,255,.78);backdrop-filter:blur(8px);padding:8px;box-shadow:0 12px 28px rgba(15,23,42,.07);}" +
      ".rbe-sheet-suggest-item{width:100%;border:1px solid var(--rbe-suggest-border,rgba(148,163,184,.32));border-left:4px solid var(--rbe-suggest-accent,#2563eb);border-radius:9px;padding:9px 10px;text-align:left;cursor:pointer;font:inherit;display:grid;gap:3px;transition:transform .12s ease,filter .12s ease,box-shadow .12s ease,background .12s ease;}" +
      ".rbe-sheet-suggest-item:hover,.rbe-sheet-suggest-item:focus{transform:translateY(-1px);filter:saturate(1.08) brightness(1.01);box-shadow:0 10px 22px rgba(15,23,42,.12);outline:2px solid rgba(37,99,235,.18);outline-offset:1px;background:var(--rbe-suggest-hover,rgba(37,99,235,.14))!important;}" +
      ".rbe-sheet-suggest-title{font-size:12px;font-weight:900;letter-spacing:.01em;opacity:.92;}" +
      ".rbe-sheet-suggest-prompt{font-size:13px;font-weight:800;line-height:1.35;}" +
      ".rbe-sheet-suggest-meta{font-size:11px;font-weight:750;opacity:.78;}" +
      ".rbe-sheet-manual-btn{border:0;border-radius:7px;background:#eef4ff;color:#2563eb;padding:0;font-weight:850;cursor:pointer;width:38px;min-width:38px;height:38px;min-height:38px;display:inline-grid;place-items:center;}" +
      ".rbe-sheet-manual-btn .rbe-svg-icon{width:18px;height:18px;stroke-width:3.2;color:#2563eb;}" +
      ".rbe-sheet-manual-btn.is-checking .rbe-svg-icon{display:none;}" +
      ".rbe-sheet-manual-btn.is-checking:before{content:'';width:17px;height:17px;border:2px solid #bfdbfe;border-top-color:#2563eb;border-radius:999px;animation:rbe-sheet-spin .72s linear infinite;}" +
      ".rbe-sheet-manual-btn:hover{background:#dbeafe;color:#1d4ed8;}" +
      ".rbe-sheet-manual-btn:disabled{opacity:.58;cursor:not-allowed;}" +
      "@keyframes rbe-sheet-spin{to{transform:rotate(360deg);}}" +
      ".rbe-sheet-modal-backdrop{position:fixed;inset:0;z-index:2147483000;display:grid;place-items:center;background:rgba(15,23,42,.58);padding:18px;}" +
      ".rbe-sheet-modal{width:min(480px,100%);border:1px solid var(--rbe-border,#d7dee8);border-radius:8px;background:var(--rbe-surface,#fff);color:var(--rbe-text,#172033);box-shadow:0 24px 80px rgba(15,23,42,.28);overflow:hidden;}" +
      ".rbe-sheet-modal-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 16px;border-bottom:1px solid var(--rbe-border,#d7dee8);}" +
      ".rbe-sheet-modal-head strong{font-size:16px;font-weight:850;}" +
      ".rbe-sheet-modal-close{border:0;background:transparent;color:var(--rbe-muted,#667085);width:32px;height:32px;border-radius:6px;display:inline-grid;place-items:center;cursor:pointer;}" +
      ".rbe-sheet-modal-close:hover{background:var(--rbe-surface-soft,#f1f5f9);color:var(--rbe-text,#172033);}" +
      ".rbe-sheet-modal-body{padding:16px;color:var(--rbe-text,#172033);font-size:14px;line-height:1.5;}" +
      ".rbe-sheet-modal-body p{margin:0 0 10px;}" +
      ".rbe-sheet-modal-body small{display:block;color:var(--rbe-muted,#667085);font-weight:650;}" +
      ".rbe-sheet-modal-foot{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--rbe-border,#d7dee8);background:var(--rbe-surface-soft,#f8fafc);}" +
      ".rbe-sheet-modal-foot button{border:1px solid var(--rbe-border,#d7dee8);border-radius:7px;background:var(--rbe-surface,#fff);color:var(--rbe-text,#172033);padding:8px 12px;font-weight:800;cursor:pointer;display:inline-flex;align-items:center;gap:7px;}" +
      ".rbe-sheet-modal-foot button[data-primary]{border-color:#2563eb;background:#2563eb;color:#fff;}" +
      ".rbe-sheet-modal-foot button:hover{filter:brightness(.98);}" +
      ".rbe.rbe-theme-dark .rbe-sheet-drop{background:#111827;border-color:#7c3cff;}.rbe.rbe-theme-dark .rbe-sheet-actions button{color:#e5e7eb;}.rbe.rbe-theme-dark .rbe-sheet-error{background:#3b1218;color:#fecaca;border-color:#7f1d1d;}" +
      ".rbe.rbe-theme-dark .rbe-sheet-manual-input{background:#111827;color:#e5e7eb;border-color:#334155;}.rbe.rbe-theme-dark .rbe-sheet-manual-credit{background:#172554;color:#bfdbfe;border-color:#1d4ed8;}.rbe.rbe-theme-dark .rbe-sheet-lint-chip{filter:saturate(.95) brightness(.92);}.rbe.rbe-theme-dark .rbe-sheet-suggest{background:rgba(15,23,42,.68);border-color:#334155;}.rbe.rbe-theme-dark .rbe-sheet-suggest-item{color:#e5e7eb!important;}.rbe.rbe-theme-dark .rbe-sheet-modal{background:#0f172a;color:#e5e7eb;border-color:#334155;}.rbe.rbe-theme-dark .rbe-sheet-modal-foot{background:#111827;border-color:#334155;}.rbe.rbe-theme-dark .rbe-sheet-modal-foot button{background:#1f2937;color:#e5e7eb;border-color:#475569;}.rbe.rbe-theme-dark .rbe-sheet-modal-foot button[data-primary]{background:#2563eb;color:#fff;border-color:#2563eb;}" +
      "@media(max-width:720px){.rbe-sheet-meta{grid-template-columns:repeat(2,minmax(0,1fr));}.rbe-sheet-column{grid-template-columns:minmax(0,1fr);}.rbe-sheet-head{align-items:flex-start;flex-direction:column;}.rbe-sheet-badges{justify-content:flex-start;}.rbe-sheet-manual{grid-template-columns:minmax(0,1fr) auto;}.rbe-sheet-manual-credit{font-size:11px;padding:0 8px;}}" +
      "@media(max-width:520px){.rbe-sheet-manual-row{grid-template-columns:minmax(0,1fr);}.rbe-sheet-manual-credit{justify-self:start;min-height:28px;height:auto;}}";
    document.head.appendChild(style);
  }

  function endpoint(name) {
    if (global.BlockWriteAIExamplePremium && typeof global.BlockWriteAIExamplePremium.sheetAnalyzerEndpoint === "function") {
      return global.BlockWriteAIExamplePremium.sheetAnalyzerEndpoint(name);
    }
    if (global.BlockWriteAIExamplePremium && typeof global.BlockWriteAIExamplePremium.action === "function") {
      return global.BlockWriteAIExamplePremium.action("sheet_analyzer_" + name);
    }
    var endpoints = BlockWriteAI.platformEndpoints || {};
    if (typeof endpoints.sheetAnalyzerEndpoint === "function") return endpoints.sheetAnalyzerEndpoint(name);
    if (endpoints.sheetAnalyzerEndpoint) return sheetAnalyzerActionEndpoint(endpoints.sheetAnalyzerEndpoint, name);
    if (endpoints.cloudProxy) return sheetAnalyzerActionEndpoint(endpoints.cloudProxy, name);
    if (endpoints.premiumPlugins) return sheetAnalyzerActionEndpoint(endpoints.premiumPlugins, name);
    return "api/premium_proxy.php?action=sheet_analyzer_" + name;
  }

  function sheetAnalyzerActionEndpoint(base, name) {
    var action = "sheet_analyzer_" + String(name || "");
    base = String(base || "").trim();
    if (!base) return "api/premium_proxy.php?action=" + encodeURIComponent(action);
    if (/[?&](?:action|route)=/i.test(base)) {
      return base.replace(/([?&](?:action|route)=)[^&#]*/i, "$1" + encodeURIComponent(action));
    }
    var separator = base.indexOf("?") >= 0 ? "&" : "?";
    return base + separator + "action=" + encodeURIComponent(action);
  }

  function qualityEndpoint(action) {
    action = "ai_quality_" + String(action || "rules");
    if (global.BlockWriteAIExamplePremium && typeof global.BlockWriteAIExamplePremium.action === "function") {
      return global.BlockWriteAIExamplePremium.action(action);
    }
    var endpoints = BlockWriteAI.platformEndpoints || {};
    if (endpoints.cloudProxy) return premiumActionEndpoint(endpoints.cloudProxy, action);
    if (endpoints.premiumPlugins) return premiumActionEndpoint(endpoints.premiumPlugins, action);
    return "api/blockwriteai-premium.php?action=" + encodeURIComponent(action);
  }

  function premiumActionEndpoint(base, action) {
    base = String(base || "").trim();
    if (!base) return "";
    if (/[?&](?:action|route)=/i.test(base)) {
      return base.replace(/([?&](?:action|route)=)[^&#]*/i, "$1" + encodeURIComponent(action));
    }
    var separator = base.indexOf("?") >= 0 ? "&" : "?";
    return base + separator + "action=" + encodeURIComponent(action);
  }

  function localStorageValue(key) {
    try {
      return global.localStorage ? global.localStorage.getItem(key) || "" : "";
    } catch (error) {
      return "";
    }
  }

  function aiUsageEndpoint(editor) {
    if (editor && editor.premiumOptions && editor.premiumOptions.usageEndpoint) {
      return editor.premiumOptions.usageEndpoint;
    }
    if (global.BlockWriteAIExamplePremium && typeof global.BlockWriteAIExamplePremium.action === "function") {
      return global.BlockWriteAIExamplePremium.action("ai_usage");
    }
    if (global.BlockWriteAI && BlockWriteAI.platformEndpoints && BlockWriteAI.platformEndpoints.aiUsage) {
      return BlockWriteAI.platformEndpoints.aiUsage;
    }
    return "api/blockwriteai-premium.php?action=ai_usage";
  }

  function aiUsageDetailEndpoint(editor) {
    if (editor && editor.premiumOptions && editor.premiumOptions.usageDetailEndpoint) {
      return editor.premiumOptions.usageDetailEndpoint;
    }
    var endpoint = aiUsageEndpoint(editor);
    if (/[?&](route|action)=ai_usage(?:[&#]|$)/i.test(endpoint)) {
      return endpoint.replace(/([?&](?:route|action)=)ai_usage([&#]?)/i, "$1ai_usage_detail$2");
    }
    if (/\/api\/ai_usage\.php(?:[?#].*)?$/i.test(endpoint)) {
      return endpoint.replace(/\/api\/ai_usage\.php(\?.*)?$/i, "/api/ai_usage_detail.php$1");
    }
    return "api/blockwriteai-premium.php?action=ai_usage_detail";
  }

  function premiumUsageAuth(editor) {
    var options = editor && editor.premiumOptions || {};
    var serverMode = !!options.serverMode;
    var licenseKey = serverMode ? "" : options.licenseKey || localStorageValue("blockwriteai_license_key") || localStorageValue("bwai_license_key") || global.BlockWriteAILicenseKey || "";
    var authToken = serverMode ? "" : options.authorizationToken || localStorageValue("bwai_authorization_token") || global.BlockWriteAIAuthorizationToken || "";
    var payload = {};
    if (authToken) {
      payload.authorization_token = authToken;
    } else if (licenseKey) {
      payload.license_key = licenseKey;
    }
    if (options.sessionToken) payload.session_token = options.sessionToken;
    return payload;
  }

  function usageEventId(usage) {
    if (!usage) return null;
    if (usage.usage_event_id) return usage.usage_event_id;
    if (usage.event_id) return usage.event_id;
    if (usage.usage_result && usage.usage_result.event_id) return usage.usage_result.event_id;
    return null;
  }

  function sheetAnalysisBillingPrompt(deep) {
    return deep
      ? "Deep Analyze uploaded spreadsheet with full-dataset compact analytics and create editor blocks."
      : "Analyze uploaded spreadsheet with full-dataset compact analytics and create editor blocks.";
  }

  function sheetAnalysisBillingAction(deep) {
    return deep ? "sheet_analyzer_deep_analysis" : "sheet_analyzer_normal_analysis";
  }

  function sheetAnalysisBillingCredits(deep) {
    return deep ? 25 : 15;
  }

  function startSheetAnalysisUsage(editor, metadata, deep) {
    if (!editor || typeof editor.consumePremiumUsage !== "function") return Promise.resolve(null);
    var action = sheetAnalysisBillingAction(deep);
    return editor.consumePremiumUsage("ai", {
      action: action,
      route: action,
      source: "sheet_analyzer",
      premium_feature: "sheet_analyzer",
      block_type: "sheetAnalyzer",
      editor_block_type: "sheetAnalyzer",
      credits_required: sheetAnalysisBillingCredits(deep),
      prompt: sheetAnalysisBillingPrompt(deep),
      sheet_name: metadata && metadata.fileName || metadata && metadata.name || "",
      row_count: metadata && metadata.rowCount || 0,
      column_count: metadata && metadata.columnCount || metadata && metadata.headings && metadata.headings.length || 0
    });
  }

  function updateSheetAnalysisUsage(editor, usage, details) {
    var eventId = usageEventId(usage);
    if (!eventId || typeof fetch !== "function") return Promise.resolve(null);
    var body = Object.assign({}, premiumUsageAuth(editor), details || {}, {
      event_id: eventId,
      feature: "ai"
    });
    return fetch(aiUsageDetailEndpoint(editor), {
      method: "POST",
      headers: { "Content-Type": "application/json", Accept: "application/json" },
      body: JSON.stringify(body)
    }).then(function (response) {
      return response.json().catch(function () { return {}; }).then(function (payload) {
        if (!response.ok || payload.ok === false) {
          throw new Error(payload.error || payload.message || "AI usage result update failed.");
        }
        return payload;
      });
    });
  }

  function completeSheetAnalysisUsage(editor, usage, metadata, deep, result, startedAt) {
    var usedLocalFallback = !!(result && result.warning);
    return updateSheetAnalysisUsage(editor, usage, {
      status: "completed",
      response_type: usedLocalFallback ? "sheet_analysis_local_fallback" : "sheet_analysis",
      model: "gpt-5-nano",
      response_source: usedLocalFallback ? "fallback completed" : "openai",
      route: sheetAnalysisBillingAction(deep),
      action: sheetAnalysisBillingAction(deep),
      source: "sheet_analyzer",
      premium_feature: "sheet_analyzer",
      block_type: "sheetAnalyzer",
      editor_block_type: "sheetAnalyzer",
      credits_required: sheetAnalysisBillingCredits(deep),
      prompt: sheetAnalysisBillingPrompt(deep),
      response: "AI Sheet Analyzer analysis completed.",
      blocks_generated: result && result.blocks && result.blocks.length || 0,
      duration_ms: startedAt ? Date.now() - startedAt : undefined,
      sheet_name: metadata && metadata.fileName || metadata && metadata.name || "",
      row_count: metadata && metadata.rowCount || 0,
      column_count: metadata && metadata.columnCount || metadata && metadata.headings && metadata.headings.length || 0
    });
  }

  function failSheetAnalysisUsage(editor, usage, metadata, deep, error, startedAt) {
    return updateSheetAnalysisUsage(editor, usage, {
      status: "failed",
      response_type: "sheet_analysis",
      model: "gpt-5-nano",
      route: sheetAnalysisBillingAction(deep),
      action: sheetAnalysisBillingAction(deep),
      source: "sheet_analyzer",
      premium_feature: "sheet_analyzer",
      block_type: "sheetAnalyzer",
      editor_block_type: "sheetAnalyzer",
      credits_required: sheetAnalysisBillingCredits(deep),
      prompt: sheetAnalysisBillingPrompt(deep),
      error_reason: error && error.message || "AI Sheet Analyzer analysis failed.",
      duration_ms: startedAt ? Date.now() - startedAt : undefined,
      sheet_name: metadata && metadata.fileName || metadata && metadata.name || "",
      row_count: metadata && metadata.rowCount || 0,
      column_count: metadata && metadata.columnCount || metadata && metadata.headings && metadata.headings.length || 0
    });
  }

  function startManualAnalyticsUsage(editor, metadata, question, credits) {
    if (!editor || typeof editor.consumePremiumUsage !== "function") return Promise.resolve(null);
    return editor.consumePremiumUsage("ai", {
      action: "sheet_analyzer_manual_question",
      route: "sheet_analyzer_manual_question",
      source: "sheet_analyzer",
      premium_feature: "sheet_analyzer",
      block_type: "sheetAnalyzer",
      editor_block_type: "sheetAnalyzer",
      credits_required: Math.max(1, Number(credits) || 1),
      exact_credits_required: true,
      prompt: question,
      sheet_name: metadata && metadata.fileName || metadata && metadata.name || "",
      row_count: metadata && metadata.rowCount || 0,
      column_count: metadata && metadata.columnCount || metadata && metadata.headings && metadata.headings.length || 0
    });
  }

  function completeManualAnalyticsUsage(editor, usage, metadata, question, credits, blocks, startedAt) {
    return updateSheetAnalysisUsage(editor, usage, {
      status: "completed",
      response_type: "sheet_manual_local",
      model: "local",
      response_source: "manual analytics local completed",
      route: "sheet_analyzer_manual_question",
      action: "sheet_analyzer_manual_question",
      source: "sheet_analyzer",
      premium_feature: "sheet_analyzer",
      block_type: "sheetAnalyzer",
      editor_block_type: "sheetAnalyzer",
      credits_required: Math.max(1, Number(credits) || 1),
      exact_credits_required: true,
      prompt: question,
      response: "AI Sheet Analyzer manual analytics completed.",
      blocks_generated: blocks && blocks.length || 0,
      duration_ms: startedAt ? Date.now() - startedAt : undefined,
      sheet_name: metadata && metadata.fileName || metadata && metadata.name || "",
      row_count: metadata && metadata.rowCount || 0,
      column_count: metadata && metadata.columnCount || metadata && metadata.headings && metadata.headings.length || 0
    });
  }

  function completeManualAiUsage(editor, usage, metadata, question, credits, blocks, startedAt) {
    return updateSheetAnalysisUsage(editor, usage, {
      status: "completed",
      response_type: "sheet_followup",
      model: usage && usage.model || "gpt-5-nano",
      response_source: "openai",
      route: "sheet_analyzer_manual_question",
      action: "sheet_analyzer_manual_question",
      source: "sheet_analyzer",
      premium_feature: "sheet_analyzer",
      block_type: "sheetAnalyzer",
      editor_block_type: "sheetAnalyzer",
      credits_required: Math.max(1, Number(credits) || 1),
      exact_credits_required: true,
      prompt: question,
      response: "AI Sheet Analyzer manual analytics completed.",
      input_tokens: Number(usage && usage.input_tokens || 0),
      output_tokens: Number(usage && usage.output_tokens || 0),
      total_tokens: Number(usage && usage.total_tokens || 0),
      blocks_generated: blocks && blocks.length || 0,
      duration_ms: startedAt ? Date.now() - startedAt : undefined,
      sheet_name: metadata && metadata.fileName || metadata && metadata.name || "",
      row_count: metadata && metadata.rowCount || 0,
      column_count: metadata && metadata.columnCount || metadata && metadata.headings && metadata.headings.length || 0
    });
  }

  function failManualAiUsage(editor, usage, question, error) {
    return updateSheetAnalysisUsage(editor, usage, {
      status: "failed",
      response_type: "sheet_followup",
      model: usage && usage.model || "gpt-5-nano",
      route: "sheet_analyzer_manual_question",
      action: "sheet_analyzer_manual_question",
      source: "sheet_analyzer",
      credits_required: Math.max(1, Number(usage && usage.credits_used) || 1),
      exact_credits_required: true,
      prompt: question,
      error_reason: error && error.message || error || "Manual analytics validation failed."
    });
  }

  function fetchQualityRules(feature, prompt) {
    if (typeof fetch !== "function") return Promise.resolve([]);
    var controller = typeof AbortController !== "undefined" ? new AbortController() : null;
    var timeout = setTimeout(function () {
      if (controller) controller.abort();
    }, 2500);
    return fetch(qualityEndpoint("rules"), {
      method: "POST",
      headers: { "Content-Type": "application/json", Accept: "application/json" },
      signal: controller ? controller.signal : undefined,
      body: JSON.stringify({ feature: feature || "sheet_analyzer", prompt: String(prompt || "") })
    }).then(function (response) {
      return response.json().catch(function () { return {}; });
    }).then(function (payload) {
      return Array.isArray(payload.rules) ? payload.rules : [];
    }).catch(function () {
      return [];
    }).finally(function () {
      clearTimeout(timeout);
    });
  }

  function qualityRulesText(rules) {
    return (Array.isArray(rules) ? rules : []).map(function (rule, index) {
      return (index + 1) + ". " + String(rule.instruction || rule.intent_type || rule.pattern || "").trim();
    }).filter(function (line) {
      return /\.\s+\S/.test(line);
    }).join("\n");
  }

  function ruleText(rule) {
    if (!rule) return "";
    var parts = [];
    ["instruction", "intent_type", "pattern", "prompt", "route", "status", "outcome", "reason", "message"].forEach(function (key) {
      if (rule[key] != null) parts.push(String(rule[key]));
    });
    if (rule.metadata && typeof rule.metadata === "object") {
      ["suggested_prompt", "original_prompt", "route", "reason", "validation_issue"].forEach(function (key) {
        if (rule.metadata[key] != null) parts.push(String(rule.metadata[key]));
      });
    }
    return parts.join(" ").replace(/\s+/g, " ").trim();
  }

  function tokenOverlapScore(a, b) {
    var stop = { "the": true, "and": true, "for": true, "with": true, "from": true, "into": true, "only": true, "show": true, "create": true, "chart": true, "table": true, "using": true, "rows": true, "columns": true, "values": true };
    var left = String(a || "").toLowerCase().match(/[a-z0-9_]+/g) || [];
    var right = {};
    (String(b || "").toLowerCase().match(/[a-z0-9_]+/g) || []).forEach(function (token) {
      if (token.length > 2 && !stop[token]) right[token] = true;
    });
    var hits = 0;
    left.forEach(function (token) {
      if (token.length > 2 && !stop[token] && right[token]) hits += 1;
    });
    return hits;
  }

  function promptIntentKey(value) {
    var stop = { a: true, an: true, the: true, and: true, or: true, to: true, by: true, where: true, are: true, is: true, be: true, as: true, this: true, that: true, my: true, please: true };
    return (String(value || "").toLowerCase().replace(/[`"']/g, "").match(/[a-z0-9_]+/g) || []).map(function (token) {
      return token.length > 3 && /s$/.test(token) ? token.slice(0, -1) : token;
    }).filter(function (token) {
      return token && !stop[token];
    }).join(" ");
  }

  function qualityRuleSuppressesSuggestion(rule, suggestion, question) {
    var text = ruleText(rule);
    if (!text || !suggestion) return false;
    var lower = text.toLowerCase();
    var negative = /(?:suppress|do not suggest|bad suggestion|hide suggestion|block suggestion|avoid suggesting|failed suggestion|wrong suggestion|incorrect suggestion|unsupported suggestion|unvalidated suggestion|hang|unresponsive|not expected)/i.test(lower);
    if (!negative) return false;
    var target = [question, suggestion.prompt, suggestion.title, suggestion.reason, suggestion.deterministicRoute].join(" ");
    var pattern = String(rule.pattern || rule.intent_type || "").trim();
    if (String(rule.intent_type || "") === "learned_failed_suggestion") {
      return !!(pattern && promptIntentKey(pattern) === promptIntentKey(suggestion.prompt));
    }
    if (pattern && target.toLowerCase().indexOf(pattern.toLowerCase()) >= 0) return true;
    if (suggestion.deterministicRoute && lower.indexOf(String(suggestion.deterministicRoute).toLowerCase()) >= 0) return true;
    return tokenOverlapScore(text, target) >= 4;
  }

  function filterSuggestionsByQualityRules(suggestions, rules, question) {
    if (!Array.isArray(suggestions) || !suggestions.length) return [];
    if (!Array.isArray(rules) || !rules.length) return suggestions;
    return suggestions.filter(function (suggestion) {
      return !rules.some(function (rule) {
        return qualityRuleSuppressesSuggestion(rule, suggestion, question);
      });
    });
  }

  function filterPreviouslyAcceptedSuggestions(suggestions, acceptedSuggestions, question) {
    if (!Array.isArray(suggestions) || !suggestions.length) return [];
    if (!acceptedSuggestions || !acceptedSuggestions.length) return suggestions;
    var current = promptIntentKey(question);
    return suggestions.filter(function (suggestion) {
      var prompt = promptIntentKey(suggestion && suggestion.prompt);
      if (!prompt) return false;
      return !acceptedSuggestions.some(function (accepted) {
        return accepted &&
          accepted.suppress_reuse === true &&
          promptIntentKey(accepted.original_prompt) === current &&
          promptIntentKey(accepted.suggested_prompt) === prompt;
      });
    });
  }

  function manualSuggestionSemanticKey(suggestion) {
    suggestion = suggestion || {};
    var promptText = String(suggestion.prompt || "");
    var prompt = promptText
      .replace(/\buse\s+parsed\s+workbook\s+metadata\s+only\b/ig, " ")
      .replace(/\buse\s+the\s+parsed\s+workbook\s+metadata\b/ig, " ")
      .replace(/\buse\s+all\s+parsed\s+rows\b/ig, " ")
      .replace(/\breturn\s+(?:only\s+)?row\s+count\s+and\s+column\s+count\b/ig, " ")
      .replace(/\bdo\s+not\s+group[^.]*\.?/ig, " ")
      .replace(/\s+/g, " ")
      .trim();
    if (/\b(row\s+count|rows?\s+and\s+columns?|column\s+count|columns?\s+and\s+rows?)\b/i.test(promptText)) {
      prompt = "row_column_count";
    }
    return [String(suggestion.intentType || suggestion.deterministicRoute || "").toLowerCase(), columnKey(suggestion.metric || ""), columnKey(suggestion.groupBy || ""), String(suggestion.aggregation || "").toLowerCase(), promptIntentKey(prompt)].join("|");
  }

  function dedupeManualSuggestions(suggestions) {
    var seen = {};
    return (Array.isArray(suggestions) ? suggestions : []).filter(function (suggestion) {
      var key = manualSuggestionSemanticKey(suggestion);
      if (!key || seen[key]) return false;
      seen[key] = true;
      return true;
    });
  }

  function suggestionExecutionContract(suggestion) {
    suggestion = suggestion || {};
    var executionContract = suggestion.executionPlan && suggestion.executionPlan.contract || {};
    return Object.assign({}, executionContract, suggestion.contract || {}, {
      prompt: suggestion.prompt || executionContract.prompt || "",
      intentType: suggestion.intentType || executionContract.intentType || suggestion.deterministicRoute || "",
      metric: suggestion.metric || executionContract.metric || "",
      groupBy: suggestion.groupBy || executionContract.groupBy || "",
      aggregation: suggestion.aggregation || executionContract.aggregation || "",
      creditEstimate: suggestion.creditEstimate || executionContract.creditEstimate || suggestion.estimate && suggestion.estimate.credits || 0
    });
  }

  function suggestionPreservesLiveContract(metadata, question, suggestion) {
    if (!metadata || !question || !suggestion) return false;
    var live = manualIntentContract(metadata, question);
    var stored = normalizeStoredIntentContract(metadata, question, suggestionExecutionContract(suggestion));
    if (!live || !stored) return false;
    function comparableIntent(intent) {
      intent = String(intent || "").toLowerCase();
      if (/^(grouped_analytics|grouped_metric|grouped_count)$/.test(intent)) return "grouped";
      if (/^(scalar_metric|scalar_count|scalar_rate)$/.test(intent)) return "scalar";
      return intent;
    }
    function comparableAgg(aggregation) {
      aggregation = String(aggregation || "").toLowerCase();
      if (/^(sum|total)$/.test(aggregation)) return "sum";
      if (/^(avg|average|mean)$/.test(aggregation)) return "average";
      if (/^(count|row_count|filtered_count)$/.test(aggregation)) return "count";
      return aggregation;
    }
    var liveIntent = comparableIntent(live.intentType);
    var storedIntent = comparableIntent(stored.intentType);
    if (liveIntent && storedIntent && liveIntent !== storedIntent) return false;
    var liveMetric = columnKey(live.metric || "");
    var storedMetric = columnKey(stored.metric || "");
    if (liveMetric && liveMetric !== "__row_count" && storedMetric && storedMetric !== liveMetric) return false;
    var liveGroup = columnKey(live.groupBy || "");
    var storedGroup = columnKey(stored.groupBy || "");
    if (!liveGroup && storedGroup) return false;
    if (liveGroup && storedGroup && liveGroup !== storedGroup) return false;
    var liveAgg = comparableAgg(live.aggregation || aggregationMode(question));
    var storedAgg = comparableAgg(stored.aggregation || "");
    if (liveAgg && storedAgg && liveAgg !== storedAgg) return false;
    if (!live.percentageRequired && (stored.percentageRequired || /share|contribution|percentage|percent/i.test(String(stored.prompt || suggestion.prompt || "")))) return false;
    return validateSuggestionPreservesOriginalIntent(metadata, question, suggestion.prompt || "").ok;
  }

  function promptModeContract(prompt) {
    var original = String(prompt || "").trim();
    var working = original;
    var prefixes = [];
    var suffixes = [];
    var prefixMap = {
      "EXPOSE": {
        id: "expose",
        instruction: "EXPOSE mode: reveal hidden assumptions, gaps, contradictions, risks, missing information, unsupported claims, and validation issues before finalizing the answer."
      },
      "IQ200": {
        id: "deep_reasoning",
        instruction: "DEEP THINK mode: use structured multi-angle reasoning; consider objectives, constraints, alternatives, edge cases, tradeoffs, and verification checks before producing concise output."
      },
      "DEEP THINK": {
        id: "deep_reasoning",
        instruction: "DEEP THINK mode: use structured multi-angle reasoning; consider objectives, constraints, alternatives, edge cases, tradeoffs, and verification checks before producing concise output."
      },
      "DEEP DIVE": {
        id: "deep_reasoning",
        instruction: "DEEP DIVE mode: analyze deeply across causes, evidence, tradeoffs, risks, and recommended next actions."
      },
      "AUTOPSY": {
        id: "autopsy",
        instruction: "AUTOPSY mode: diagnose the subject systematically: what works, what fails, root causes, evidence, impact, missing information, and specific fixes."
      },
      "KILL CRITIC": {
        id: "kill_critic",
        instruction: "KILL CRITIC mode: act as a skeptical expert; challenge assumptions, identify failure scenarios, objections, weak evidence, risks, and concrete improvements."
      }
    };
    var changed = true;
    while (changed) {
      changed = false;
      Object.keys(prefixMap).forEach(function (name) {
        var re = new RegExp("^\\s*" + name.replace(/\s+/g, "\\s+") + "\\s*:\\s*", "i");
        if (re.test(working)) {
          var mode = prefixMap[name];
          if (!prefixes.some(function (item) { return item.id === mode.id; })) prefixes.push(mode);
          working = working.replace(re, "");
          changed = true;
        }
      });
    }
    if (/\s*:?\s*10X\s*$/i.test(working)) {
      suffixes.push({
        id: "10x",
        instruction: "10X suffix: after satisfying the base request, improve usefulness, clarity, completeness, depth, structure, and actionability. Rebuild weak output into production-ready content."
      });
      working = working.replace(/\s*:?\s*10X\s*$/i, "").trim();
    }
    var modes = prefixes.concat(suffixes);
    return {
      originalPrompt: original,
      cleanPrompt: working || original,
      modes: modes.map(function (mode) { return mode.id; }),
      instructions: modes.map(function (mode) { return mode.instruction; })
    };
  }

  function promptModeInstructions(contract) {
    contract = contract || {};
    if (!contract.instructions || !contract.instructions.length) return "";
    return "Prompt Power Modes detected: " + contract.modes.join(", ") + "\n" + contract.instructions.map(function (item, index) {
      return (index + 1) + ". " + item;
    }).join("\n");
  }

  function promptModeCreditModifier(contract) {
    contract = contract || {};
    var costs = { expose: 2, deep_reasoning: 5, autopsy: 5, kill_critic: 4, "10x": 5 };
    var seen = {};
    var total = 0;
    (Array.isArray(contract.modes) ? contract.modes : []).forEach(function (mode) {
      mode = String(mode || "").toLowerCase();
      if (seen[mode]) return;
      seen[mode] = true;
      total += costs[mode] || 0;
    });
    return Math.min(10, Math.max(0, total));
  }

  function applyPromptModeCreditCap(baseCredits, contract, cap) {
    var modifier = promptModeCreditModifier(contract);
    baseCredits = Math.max(1, parseInt(baseCredits, 10) || 1);
    return modifier > 0 ? Math.min(cap, baseCredits + modifier) : baseCredits;
  }

  function logQualityEvent(payload) {
    if (typeof fetch !== "function") return;
    try {
      fetch(qualityEndpoint("event"), {
        method: "POST",
        headers: { "Content-Type": "application/json", Accept: "application/json" },
        body: JSON.stringify(payload || {})
      }).catch(function () {});
    } catch (error) {}
  }

  function logQualityLineage(payload) {
    if (typeof fetch !== "function") return;
    try {
      fetch(qualityEndpoint("lineage"), {
        method: "POST",
        headers: { "Content-Type": "application/json", Accept: "application/json" },
        body: JSON.stringify(payload || {})
      }).catch(function () {});
    } catch (error) {}
  }

  function request(name, payload) {
    payload = Object.assign({}, payload || {});
    if (global.BlockWriteAILicenseKey && !payload.license_key) payload.license_key = global.BlockWriteAILicenseKey;
    if (global.BlockWriteAISessionToken && !payload.session_token) payload.session_token = global.BlockWriteAISessionToken;
    return fetch(endpoint(name), {
      method: "POST",
      headers: { "Content-Type": "application/json", Accept: "application/json" },
      body: JSON.stringify(payload)
    }).then(function (response) {
      return response.json().catch(function () { return {}; }).then(function (json) {
        if (!response.ok || json.ok === false) {
          var error = new Error(json.error || json.message || (response.status >= 500 ? "Sheet Analyzer gateway error." : "Sheet Analyzer request failed."));
          error.status = response.status;
          error.details = json;
          throw error;
        }
        return json;
      });
    });
  }

  function isInvalidJsonError(error) {
    return /\binvalid\s+json\b/i.test(String(error && error.message || error || ""));
  }

  function isRetryableSheetError(error) {
    return isInvalidJsonError(error) || Number(error && error.status || 0) >= 500 || /\b(502|bad gateway|gateway|temporar|timeout)\b/i.test(String(error && error.message || error || ""));
  }

  function wait(ms) {
    return new Promise(function (resolve) {
      setTimeout(resolve, ms);
    });
  }

  function requestWithRetry(name, payload) {
    var attempts = name === "analyze" ? 4 : 3;
    var delay = 450;
    function run(attempt) {
      return request(name, Object.assign({}, payload || {}, attempt > 1 ? { retry_invalid_json: true, retry_attempt: attempt } : {})).catch(function (error) {
        if (attempt >= attempts || !isRetryableSheetError(error)) throw error;
        return wait(delay * attempt).then(function () { return run(attempt + 1); });
      });
    }
    return run(1);
  }

  function localAnalysis(metadata) {
    metadata = metadata || {};
    var profile = analyticsSemanticProfile(metadata);
    var numeric = profile.metrics.map(function (item) { return item.name; }).filter(function (column) {
      var info = semanticColumnInfo(metadata, column);
      return info.isNumeric && !info.identifier && !info.demographic && info.role !== "status";
    });
    var preferredBilling = findColumnByWords(metadata, ["totalbill_inr", "total bill", "totalbill", "hospital billing", "bill amount"]);
    if (preferredBilling && numeric.indexOf(preferredBilling) >= 0) {
      numeric = [preferredBilling].concat(numeric.filter(function (column) { return column !== preferredBilling; }));
    } else if (preferredBilling && Object.prototype.hasOwnProperty.call(metadata.numericStats || {}, preferredBilling)) {
      numeric = [preferredBilling].concat(numeric.filter(function (column) { return column !== preferredBilling; }));
    }
    var dimensions = profile.dimensions.map(function (item) { return item.name; });
    var charts = smartRecommendedCharts(metadata, false);
    var rows = rowsForAnalytics(metadata);
    var missingTotal = Object.keys(metadata.missingValues || {}).reduce(function (sum, key) { return sum + (Number(metadata.missingValues[key]) || 0); }, 0);
    var quality = [];
    if (missingTotal) {
      var missingTop = Object.keys(metadata.missingValues || {}).filter(function (key) { return Number(metadata.missingValues[key]) > 0; }).sort(function (a, b) { return Number(metadata.missingValues[b]) - Number(metadata.missingValues[a]); }).slice(0, 5);
      quality.push("Missing values: " + missingTotal + " total" + (missingTop.length ? " (" + missingTop.map(function (key) { return key + ": " + metadata.missingValues[key]; }).join(", ") + ")" : "") + ".");
    }
    if (metadata.duplicateRows) quality.push("Duplicate rows: " + metadata.duplicateRows + " exact duplicate rows detected.");
    if (!quality.length) quality.push("No missing values or duplicate rows were detected by the deterministic parser.");
    var primaryMetric = canonicalPrimaryMetricColumn(metadata) || numeric.filter(function (column) {
      var info = semanticColumnInfo(metadata, column);
      return info.role !== "percentage" && !info.percentage;
    })[0] || safeDefaultMetricColumn(metadata);
    var department = findColumnByWords(metadata, ["department", "dept"]);
    var claimStatus = findColumnByWords(metadata, ["claim status", "claimstatus"]);
    var outcome = findColumnByWords(metadata, ["outcome", "patient outcome", "status"]);
    var diagnosis = findColumnByWords(metadata, ["diagnosis", "condition"]);
    var insuranceCoveragePct = findColumnByWords(metadata, ["insurancecoveragepct", "insurance coverage pct", "insurance coverage percent", "coverage percent", "coverage pct"]);
    var primaryDimension = department || dimensions.filter(function (dimension) { return dimension !== claimStatus && dimension !== outcome; })[0] || dimensions[0] || "";
    if (!charts.length && primaryDimension && primaryMetric) {
      charts.push({ chartType: "bar", title: columnLabel(primaryMetric) + " by " + columnLabel(primaryDimension), xAxis: primaryDimension, yAxis: primaryMetric, reason: "Groups the primary business metric by the strongest operational dimension." });
    }
    if (!charts.length && outcome && !isForbiddenColumn(metadata, "", outcome)) {
      charts.push({ chartType: "bar", title: columnLabel(outcome) + " Distribution", xAxis: outcome, yAxis: "", aggregation: "count", reason: "Categorical distribution generated from parsed rows." });
    }
    if (!charts.length && claimStatus && claimStatus !== outcome && !isForbiddenColumn(metadata, "", claimStatus)) {
      charts.push({ chartType: "bar", title: columnLabel(claimStatus) + " Distribution", xAxis: claimStatus, yAxis: "", aggregation: "count", reason: "Shows claim workflow status counts from parsed rows." });
    }
    var kpis = [];
    function addKpi(title, column, mode) {
      if (!column || kpis.some(function (item) { return item.column === column && item.mode === mode; })) return;
      var stats = metadata.numericStats[column] || {};
      var value = mode === "avg" ? stats.average : stats.sum;
      if (!Number.isFinite(Number(value))) return;
      kpis.push({ title: title, column: column, mode: mode, value: chartNumber(value), formula: (mode === "avg" ? "AVG" : "SUM") + " `" + column + "`" });
    }
    addKpi("Total Hospital Billing", preferredBilling || primaryMetric, "sum");
    addKpi("Total Insurance Claim", findColumnByWords(metadata, ["insuranceclaim_inr", "insurance claim", "claim amount"]), "sum");
    addKpi("Average Insurance Coverage %", insuranceCoveragePct, "avg");
    addKpi("Average Length Of Stay", findColumnByWords(metadata, ["lengthofstaydays", "length of stay", "los"]), "avg");
    addKpi("Average Satisfaction Score", findColumnByWords(metadata, ["satisfactionscore", "satisfaction score", "satisfaction"]), "avg");
    addKpi("Average Wait Time", findColumnByWords(metadata, ["waittimeminutes", "wait time", "waittime"]), "avg");
    var readmission = findColumnByWords(metadata, ["readmission30days", "readmission", "readmitted"]);
    if (readmission) {
      var readmitted = rows.filter(function (row) { return truthySheetValue(rowValue(row, readmission)); }).length;
      var denominator = rows.length || metadata.rowCount || 1;
      kpis.push({ title: "30-day Readmission Rate", column: readmission, mode: "rate", value: formatPercent(readmitted / Math.max(1, denominator) * 100), formula: "readmitted rows / encounter count x 100" });
    }
    numeric.forEach(function (column) {
      if (kpis.length >= 6) return;
      var role = semanticColumnInfo(metadata, column).role;
      addKpi((role === "measure_avg" || role === "percentage" ? "Average " : "Total ") + columnLabel(column), column, role === "measure_avg" || role === "percentage" ? "avg" : "sum");
    });
    return {
      summary: [
        "Sheet name: " + (metadata.fileName || "Uploaded sheet"),
        "Rows: " + (metadata.rowCount || 0),
        "Columns: " + (metadata.columnCount || 0),
        "Detected domain: " + profile.domain + " (" + Math.round((profile.confidence || 0) * 100) + "% confidence)",
        "Missing values: " + missingTotal,
        "Duplicate rows: " + (metadata.duplicateRows || 0)
      ],
      datasetReadiness: analysisReadinessItems(metadata),
      topFindings: topFindingItems(metadata, 4),
      dataQualityImpact: dataQualityImpactItems(metadata),
      missingFieldExpectations: missingFieldExpectationItems(metadata),
      suggestedNextQuestions: suggestedQuestionItems(metadata).slice(0, 3),
      dataQualityIssues: quality,
      formattingSuggestions: [],
      analytics: numeric.slice(0, 6).map(function (name) {
        var stats = metadata.numericStats[name] || {};
        var role = semanticColumnInfo(metadata, name).role;
        return columnLabel(name) + (role === "measure_avg" || role === "percentage" ? " average is " + chartNumber(stats.average || 0) + (role === "percentage" ? "%" : "") : " total is " + chartNumber(stats.sum || 0)) + " from all parsed rows.";
      }).concat([primaryDimension, diagnosis, outcome, claimStatus].filter(function (dimension, index, list) {
        return dimension && list.indexOf(dimension) === index && dimensions.indexOf(dimension) >= 0;
      }).slice(0, 4).map(function (dimension) {
        var top = topGroupByMetric(metadata, dimension, primaryMetric);
        return top && primaryMetric ? "Top " + columnLabel(dimension) + " by " + columnLabel(primaryMetric) + " is " + top.name + " with " + chartNumber(top.total) + " across " + top.count + " rows." : "";
      }).filter(Boolean)).slice(0, 10),
      recommendedCharts: charts,
      calculatedColumns: [],
      executiveKPIs: kpis.slice(0, 8).map(function (item) {
        return item.title + ": " + item.value + "; Formula: " + item.formula;
      }),
      anomalies: [],
      trends: [],
      segmentBreakdowns: [],
      recommendedActions: [
        missingTotal ? "Review and resolve missing values before using the analysis for final reporting." : "",
        metadata.duplicateRows ? "Review the " + metadata.duplicateRows + " duplicate rows before publishing totals." : "",
        primaryDimension && primaryMetric ? "Use the " + columnLabel(primaryMetric) + " by " + columnLabel(primaryDimension) + " breakdown to identify operational areas for deeper review." : "",
        claimStatus ? "Review claim status distribution to find pending or rejected claim workflow issues." : "",
        outcome ? "Review outcome distribution alongside department and diagnosis to separate volume effects from clinical outcomes." : ""
      ].filter(Boolean).slice(0, 6),
      editorBlocks: []
    };
  }

  function localAnalysisPayload(metadata, deep) {
    var analysis = deterministicAnalysisRepair(metadata, localAnalysis(metadata), !!deep);
    return {
      analysis: analysis,
      blocks: fallbackBlocks(metadata, analysis)
    };
  }

  function analysisItemLooksBlank(item) {
    var text = plainItem(item);
    if (!text) return true;
    return /(?:^|;\s*)(?:value|revenue|profit|count|avg|average|rows|columns|total|percent)\s*:\s*(?:;|$)/i.test(text);
  }

  function sumMetric(rows, column) {
    return column ? rows.reduce(function (sum, row) {
      return sum + (numericValue(rowValue(row, column)) || 0);
    }, 0) : 0;
  }

  function uniqueCount(rows, column) {
    if (!column) return rows.length;
    var seen = {};
    rows.forEach(function (row, index) {
      var value = String(rowValue(row, column) || "").trim();
      seen[value || "__blank_" + index] = true;
    });
    return Object.keys(seen).length;
  }

  function resolveBusinessColumn(metadata, words, fallbackKind) {
    return resolveNamedColumn(metadata, words, fallbackKind || "category");
  }

  function topGroupByMetric(metadata, groupColumn, metricColumn) {
    var rows = rowsForAnalytics(metadata);
    if (!groupColumn || !metricColumn || !rows.length) return null;
    var grouped = {};
    rows.forEach(function (row) {
      var label = String(rowValue(row, groupColumn) || "Blank").trim() || "Blank";
      if (!grouped[label]) grouped[label] = { name: label, total: 0, count: 0 };
      grouped[label].total += numericValue(rowValue(row, metricColumn)) || 0;
      grouped[label].count += 1;
    });
    return topEntries(grouped, 1)[0] || null;
  }

  function canonicalPrimaryMetricColumn(metadata) {
    metadata = metadata || {};
    var profile = analyticsSemanticProfile(metadata);
    var preferredBilling = findColumnByWords(metadata, ["totalbill_inr", "total bill", "totalbill", "hospital billing", "bill amount"]);
    if (profile.domain === "healthcare" && preferredBilling && Object.prototype.hasOwnProperty.call(metadata.numericStats || {}, preferredBilling)) {
      return preferredBilling;
    }
    var metrics = profile.metrics || [];
    return (metrics.filter(function (item) { return item.role !== "percentage" && !item.percentage; })[0] || metrics[0] || {}).name || "";
  }

  function chartListValue(value) {
    if (Array.isArray(value)) return value.map(function (item) { return String(item == null ? "" : item); }).join(",");
    return String(value == null ? "" : value);
  }

  function smartRecommendedCharts(metadata, deep) {
    metadata = metadata || {};
    var profile = analyticsSemanticProfile(metadata);
    var rows = rowsForAnalytics(metadata);
    var numericStats = metadata.numericStats || {};
    var metrics = (profile.metrics || []).map(function (item) { return item.name; }).filter(function (column) {
      var info = semanticColumnInfo(metadata, column);
      return info.isNumeric && !info.identifier && !info.demographic && info.role !== "status";
    });
    var primaryMetric = canonicalPrimaryMetricColumn(metadata) || safeDefaultMetricColumn(metadata) || metrics[0] || "";
    if (primaryMetric && metrics.indexOf(primaryMetric) >= 0) metrics = [primaryMetric].concat(metrics.filter(function (column) { return column !== primaryMetric; }));
    var dimensions = (profile.dimensions || []).map(function (item) { return item.name; }).filter(function (column) {
      var info = semanticColumnInfo(metadata, column);
      return info.isCategory && !info.identifier && !info.avoidDefault && !isForbiddenColumn(metadata, "", column);
    });
    var dateColumn = (metadata.dateColumns || [])[0] || "";
    var primaryDimension = profile.primaryDimension && dimensions.indexOf(profile.primaryDimension) >= 0 ? profile.primaryDimension : (safeDefaultDimensionColumn(metadata) || dimensions[0] || "");
    var secondDimension = dimensions.filter(function (column) { return column !== primaryDimension; })[0] || "";
    var statusDimensions = dimensions.filter(function (column) {
      var info = semanticColumnInfo(metadata, column);
      return info.role === "status" || /status|state|outcome|type|category|channel|segment/i.test(info.key);
    });
    var statusDimension = statusDimensions[0] || "";
    var percentageMetric = metrics.filter(function (column) {
      var info = semanticColumnInfo(metadata, column);
      return info.role === "percentage" || info.percentage;
    })[0] || "";
    var comparableMetric = metrics.filter(function (column) {
      var info = semanticColumnInfo(metadata, column);
      return column !== primaryMetric && info.role !== "percentage" && !info.percentage && !info.demographic;
    })[0] || "";
    var charts = [];
    var seen = {};
    function add(chart) {
      if (!chart || !chart.title) return;
      var axisKey = chart.xAxis || chart.yAxis ? [chart.chartType || chart.type || "bar", chart.xAxis || "", chart.yAxis || "", chart.aggregation || ""].join("|") : "";
      var key = (axisKey || [chart.chartType || chart.type || "bar", chart.title, chart.labels || "", chart.values || ""].join("|")).toLowerCase();
      if (seen[key]) return;
      seen[key] = true;
      charts.push(chart);
    }
    if (primaryMetric && primaryDimension) {
      add({ chartType: "bar", title: columnLabel(primaryMetric) + " by " + columnLabel(primaryDimension), xAxis: primaryDimension, yAxis: primaryMetric, aggregation: "sum", reason: "Groups the primary business metric by the strongest decision dimension." });
    }
    if (deep && primaryMetric && secondDimension) {
      add({ chartType: "bar", title: columnLabel(primaryMetric) + " by " + columnLabel(secondDimension), xAxis: secondDimension, yAxis: primaryMetric, aggregation: "sum", reason: "Adds a second business breakdown so decisions do not rely on one category only." });
    }
    if (deep && primaryMetric && primaryDimension) {
      add({ chartType: "bar", title: "Top 10 " + columnLabel(primaryDimension) + " by " + columnLabel(primaryMetric), xAxis: primaryDimension, yAxis: primaryMetric, aggregation: "sum", reason: "Ranks the highest contributing groups for the primary metric." });
    }
    if (statusDimension) {
      add({ chartType: "bar", title: columnLabel(statusDimension) + " Distribution", xAxis: statusDimension, yAxis: "", aggregation: "count", reason: "Shows the mix of important categorical/status values from parsed rows." });
    }
    statusDimensions.filter(function (column) { return column !== statusDimension; }).slice(0, deep ? 2 : 1).forEach(function (column) {
      add({ chartType: "bar", title: columnLabel(column) + " Distribution", xAxis: column, yAxis: "", aggregation: "count", reason: "Shows another important categorical/status distribution from parsed rows." });
    });
    if (!statusDimension && dimensions[0]) {
      add({ chartType: "bar", title: columnLabel(dimensions[0]) + " Distribution", xAxis: dimensions[0], yAxis: "", aggregation: "count", reason: "Shows the distribution of a high-value category from parsed rows." });
    }
    if (deep && dateColumn && primaryMetric) {
      add({ chartType: "line", title: "Monthly " + columnLabel(primaryMetric) + " Trend", xAxis: dateColumn, yAxis: primaryMetric, aggregation: "sum", reason: "Shows whether the primary metric is rising, falling, or seasonal over time." });
    }
    if (deep && percentageMetric && primaryDimension) {
      add({ chartType: "bar", title: "Average " + columnLabel(percentageMetric) + " by " + columnLabel(primaryDimension), xAxis: primaryDimension, yAxis: percentageMetric, aggregation: "average", reason: "Uses average for percentage, rate, ratio, and score fields instead of summing them." });
    }
    if (deep && primaryDimension) {
      add({ chartType: "bar", title: "Volume by " + columnLabel(primaryDimension), xAxis: primaryDimension, yAxis: "", aggregation: "count", reason: "Shows row, order, encounter, ticket, or record volume by the main category." });
    }
    if (deep && primaryMetric && rows.length) {
      var idColumn = (metadata.headings || []).filter(function (column) { return semanticColumnInfo(metadata, column).identifier; })[0] || "";
      var topRows = rows.map(function (row, index) {
        return { label: idColumn ? chartCategoryLabel(rowValue(row, idColumn)) : "Row " + (index + 1), value: numericValue(rowValue(row, primaryMetric)) || 0 };
      }).sort(function (a, b) { return b.value - a.value; }).slice(0, 10);
      if (topRows.length && topRows[0].value > 0) {
        add({ chartType: "bar", title: "Top High-Value " + columnLabel(primaryMetric) + " Records", labels: topRows.map(function (item) { return item.label; }), values: topRows.map(function (item) { return chartNumber(item.value); }), reason: "Highlights the highest-value records so users can audit outliers before publishing decisions." });
      }
    }
    if (deep && primaryMetric && comparableMetric && primaryDimension) {
      add({ chartType: "comparison", title: columnLabel(primaryMetric) + " vs " + columnLabel(comparableMetric) + " by " + columnLabel(primaryDimension), xAxis: primaryDimension, yAxis: primaryMetric + "," + comparableMetric, aggregation: "sum", reason: "Compares two related numeric metrics across the same business dimension." });
    }
    if (deep) {
      var qualityLabels = [];
      var qualityValues = [];
      Object.keys(metadata.missingValues || {}).filter(function (column) { return Number(metadata.missingValues[column]) > 0; }).sort(function (a, b) {
        return Number(metadata.missingValues[b]) - Number(metadata.missingValues[a]);
      }).slice(0, 8).forEach(function (column) {
        qualityLabels.push("Missing " + columnLabel(column));
        qualityValues.push(Number(metadata.missingValues[column]) || 0);
      });
      if (metadata.duplicateRows) {
        qualityLabels.push("Duplicate Rows");
        qualityValues.push(Number(metadata.duplicateRows) || 0);
      }
      if (qualityLabels.length) {
        add({ chartType: "bar", title: "Data Quality Issues", labels: qualityLabels, values: qualityValues.map(chartNumber), reason: "Summarizes missing fields and duplicate records that may affect analysis reliability." });
      }
    }
    return charts.slice(0, deep ? 10 : 3);
  }

  function analysisQualityFacts(metadata) {
    metadata = metadata || {};
    var rows = rowsForAnalytics(metadata);
    var rowCount = metadata.rowCount || rows.length || 0;
    var columnCount = metadata.columnCount || (metadata.headings || []).length || 0;
    var missingTotal = Object.keys(metadata.missingValues || {}).reduce(function (sum, key) { return sum + (Number(metadata.missingValues[key]) || 0); }, 0);
    var missingTop = Object.keys(metadata.missingValues || {}).filter(function (key) { return Number(metadata.missingValues[key]) > 0; }).sort(function (a, b) { return Number(metadata.missingValues[b]) - Number(metadata.missingValues[a]); }).slice(0, 5);
    var duplicateRows = Number(metadata.duplicateRows || 0);
    var totalCells = Math.max(1, rowCount * Math.max(1, columnCount));
    var missingPenalty = Math.min(25, missingTotal / totalCells * 1000);
    var duplicatePenalty = Math.min(25, duplicateRows / Math.max(1, rowCount) * 250);
    var score = Math.max(0, Math.min(100, Math.round(100 - missingPenalty - duplicatePenalty)));
    return { rowCount: rowCount, columnCount: columnCount, missingTotal: missingTotal, missingTop: missingTop, duplicateRows: duplicateRows, readinessScore: score };
  }

  function analysisReadinessItems(metadata) {
    var facts = analysisQualityFacts(metadata);
    var profile = analyticsSemanticProfile(metadata || {});
    var primaryMetric = canonicalPrimaryMetricColumn(metadata || {}) || profile.primaryMetric || "";
    var primaryConfidence = Math.round(Math.min(99, Math.max(70, ((profile.confidence || 0.75) * 100) + (primaryMetric ? 6 : -8))));
    return [
      "Dataset Readiness Score: " + facts.readinessScore + "/100 based on missing values, duplicate rows, parsed columns, and row volume.",
      primaryMetric ? "Primary Metric Confidence: " + primaryConfidence + "% - selected " + columnLabel(primaryMetric) + " as the main business metric for this sheet." : "Primary Metric Confidence: Review needed - no clear primary numeric business metric was detected."
    ];
  }

  function topFindingItems(metadata, limit) {
    metadata = metadata || {};
    var facts = analysisQualityFacts(metadata);
    var primaryMetric = canonicalPrimaryMetricColumn(metadata);
    var profile = analyticsSemanticProfile(metadata);
    var primaryDimension = findColumnByWords(metadata, ["department", "category", "segment", "city", "region", "product"]) || profile.primaryDimension;
    var diagnosis = findColumnByWords(metadata, ["diagnosis", "condition"]);
    var procedure = findColumnByWords(metadata, ["procedure", "treatment"]);
    var outcome = findColumnByWords(metadata, ["outcome", "status"]);
    var items = [];
    if (primaryMetric) {
      var stats = metadata.numericStats && metadata.numericStats[primaryMetric] || {};
      items.push(columnLabel(primaryMetric) + " total is " + chartNumber(stats.sum || 0) + " across " + facts.rowCount + " parsed rows.");
      [primaryDimension, diagnosis, procedure, outcome].filter(function (dimension, index, list) { return dimension && list.indexOf(dimension) === index; }).forEach(function (dimension) {
        if (items.length >= (limit || 4)) return;
        var top = topGroupByMetric(metadata, dimension, primaryMetric);
        if (top) items.push("Top " + columnLabel(dimension) + " by " + columnLabel(primaryMetric) + " is " + top.name + " with " + chartNumber(top.total) + " across " + top.count + " rows.");
      });
    }
    if (items.length < (limit || 4) && (facts.missingTotal || facts.duplicateRows)) {
      items.push("Data quality review needed: " + facts.missingTotal + " missing values and " + facts.duplicateRows + " duplicate rows detected.");
    }
    return items.slice(0, limit || 4);
  }

  function dataQualityImpactItems(metadata) {
    var facts = analysisQualityFacts(metadata);
    var claimStatus = findColumnByWords(metadata || {}, ["claimstatus", "claim status"]);
    var city = findColumnByWords(metadata || {}, ["city", "location"]);
    var items = [];
    if (facts.missingTotal) {
      items.push("Missing values may bias segment-level reporting, especially in " + facts.missingTop.map(columnLabel).join(", ") + ".");
    }
    if (claimStatus && Number((metadata.missingValues || {})[claimStatus] || 0)) {
      items.push("Blank " + columnLabel(claimStatus) + " values can weaken workflow, approval, pending, or rejection analysis.");
    }
    if (city && Number((metadata.missingValues || {})[city] || 0)) {
      items.push("Missing " + columnLabel(city) + " values can understate geographic or location-level trends.");
    }
    if (facts.duplicateRows) {
      items.push("Duplicate rows may inflate totals, counts, and category rankings until deduplication rules are confirmed.");
    }
    if (!items.length) items.push("No major missing-value or duplicate-row impact was detected by the deterministic parser.");
    return items.slice(0, 5);
  }

  function missingFieldExpectation(metadata, column) {
    var key = columnKey(column);
    var info = semanticColumnInfo(metadata || {}, column);
    if (/claimstatus|claim_status|approvalstatus|paymentstatus/.test(key)) return "expected status labels such as Approved, Pending, Rejected, Not Applicable, or another workflow state.";
    if (/city|location|region|state|country/.test(key)) return "expected location/category labels; use a standard Unknown or Blank category if the value cannot be recovered.";
    if (/satisfaction|score|rating/.test(key)) return "expected numeric rating/score values in the same scale as the column, or a documented missing-score flag.";
    if (/date|time/.test(key)) return "expected valid date/time values using the same format as the rest of the sheet.";
    if (/department|category|segment|type|status|outcome/.test(key) || info.isCategory) return "expected categorical labels from the same controlled list used by populated rows.";
    if (info.isNumeric) return "expected numeric values; impute only when a clear business rule exists, otherwise keep as Blank/Unknown for reporting.";
    return "expected values consistent with populated rows in this column; standardize blanks before final reporting.";
  }

  function missingFieldExpectationItems(metadata) {
    var missing = metadata && metadata.missingValues || {};
    return Object.keys(missing).filter(function (column) {
      return Number(missing[column] || 0) > 0;
    }).sort(function (a, b) {
      return Number(missing[b]) - Number(missing[a]);
    }).slice(0, 6).map(function (column) {
      return columnLabel(column) + " has " + chartNumber(missing[column]) + " missing values; " + missingFieldExpectation(metadata, column);
    });
  }

  function suggestedQuestionItems(metadata) {
    metadata = metadata || {};
    var primaryMetric = canonicalPrimaryMetricColumn(metadata);
    var department = findColumnByWords(metadata, ["department", "category", "segment"]);
    var procedure = findColumnByWords(metadata, ["procedure", "product", "item"]);
    var claimStatus = findColumnByWords(metadata, ["claimstatus", "claim status", "status"]);
    var wait = findColumnByWords(metadata, ["waittimeminutes", "wait time"]);
    var satisfaction = findColumnByWords(metadata, ["satisfactionscore", "satisfaction"]);
    var questions = [];
    if (primaryMetric && department) questions.push("Show " + columnLabel(primaryMetric) + " by " + columnLabel(department) + (procedure ? " and " + columnLabel(procedure) : "") + ".");
    if (primaryMetric) questions.push("Find the top high-value " + columnLabel(primaryMetric) + " outlier rows with supporting columns.");
    if (wait && satisfaction && department) questions.push("Compare average wait time and satisfaction score by " + columnLabel(department) + ".");
    if (claimStatus) questions.push("Show " + columnLabel(claimStatus) + " distribution with Blank records separated.");
    return questions.slice(0, 5);
  }

  function opportunityItems(metadata) {
    metadata = metadata || {};
    var primaryMetric = canonicalPrimaryMetricColumn(metadata);
    var department = findColumnByWords(metadata, ["department", "category", "segment"]);
    var claimStatus = findColumnByWords(metadata, ["claimstatus", "claim status"]);
    var los = findColumnByWords(metadata, ["lengthofstaydays", "length of stay", "los"]);
    var wait = findColumnByWords(metadata, ["waittimeminutes", "wait time"]);
    var items = [];
    if (primaryMetric && department) items.push("Focus improvement reviews on high-" + columnLabel(primaryMetric) + " " + columnLabel(department) + " groups before drilling into row-level exceptions.");
    if (claimStatus) items.push("Reduce pending, rejected, or blank " + columnLabel(claimStatus) + " records to improve workflow visibility.");
    if (los) items.push("Review long " + columnLabel(los) + " cases for avoidable extended stays or data-entry issues.");
    if (wait) items.push("Compare wait-time pressure with volume and outcome metrics to prioritize operational fixes.");
    var facts = analysisQualityFacts(metadata);
    if (facts.duplicateRows || facts.missingTotal) items.push("Clean missing values and duplicate rows before final reporting or executive distribution.");
    return items.slice(0, 6);
  }

  function metricGlossaryItems(metadata) {
    metadata = metadata || {};
    var primaryMetric = canonicalPrimaryMetricColumn(metadata);
    var insuranceClaim = findColumnByWords(metadata, ["insuranceclaim_inr", "insurance claim", "claim amount"]);
    var coverage = findColumnByWords(metadata, ["insurancecoveragepct", "insurance coverage pct", "insurance coverage percent"]);
    var los = findColumnByWords(metadata, ["lengthofstaydays", "length of stay", "los"]);
    var wait = findColumnByWords(metadata, ["waittimeminutes", "wait time"]);
    var satisfaction = findColumnByWords(metadata, ["satisfactionscore", "satisfaction score"]);
    var readmission = findColumnByWords(metadata, ["readmission30days", "readmission", "readmitted"]);
    var items = [];
    if (primaryMetric) items.push(columnLabel(primaryMetric) + " = SUM `" + primaryMetric + "` over all parsed rows.");
    if (insuranceClaim && insuranceClaim !== primaryMetric) items.push(columnLabel(insuranceClaim) + " = SUM `" + insuranceClaim + "`.");
    if (coverage) items.push("Average Insurance Coverage % = AVG `" + coverage + "`; percentage columns are averaged, not summed.");
    if (los) items.push("Average Length Of Stay = AVG `" + los + "`.");
    if (satisfaction) items.push("Average Satisfaction Score = AVG `" + satisfaction + "`.");
    if (wait) items.push("Average Wait Time = AVG `" + wait + "`.");
    if (readmission) items.push("30-day Readmission Rate = readmitted rows / encounter count x 100.");
    return items.slice(0, 8);
  }

  function confidenceAssumptionItems(metadata) {
    var facts = analysisQualityFacts(metadata);
    var primaryMetric = canonicalPrimaryMetricColumn(metadata || {});
    var items = [
      "Each parsed row is treated as one record/encounter unless the user asks for distinct IDs or deduplication.",
      "Blank categorical values are labeled as Blank instead of being silently dropped.",
      "Duplicate rows are included in totals until the user asks for deduplicated analysis."
    ];
    if (primaryMetric) items.push("Primary metric selection uses semantic column names and domain rules; selected metric: " + columnLabel(primaryMetric) + ".");
    items.push("Results are based on " + facts.rowCount + " parsed rows and " + facts.columnCount + " parsed columns.");
    return items.slice(0, 6);
  }

  function groupSummaryByMetric(metadata, groupColumn, metricColumn, limit) {
    var rows = rowsForAnalytics(metadata);
    var grouped = {};
    if (!groupColumn || !rows.length) return [];
    rows.forEach(function (row) {
      var label = chartCategoryLabel(rowValue(row, groupColumn));
      if (!grouped[label]) grouped[label] = { name: label, total: 0, count: 0 };
      grouped[label].count += 1;
      if (metricColumn) grouped[label].total += numericValue(rowValue(row, metricColumn)) || 0;
      else grouped[label].value = grouped[label].count;
    });
    return topEntries(grouped, limit || 5).map(function (item) {
      return { name: item.name, total: roundNumber(item.total || 0), count: item.count || 0, average: roundNumber((item.total || 0) / Math.max(1, item.count || 0)) };
    });
  }

  function groupCountSummary(metadata, groupColumn, limit) {
    var rows = rowsForAnalytics(metadata);
    var grouped = {};
    if (!groupColumn || !rows.length) return [];
    rows.forEach(function (row) {
      var label = chartCategoryLabel(rowValue(row, groupColumn));
      if (!grouped[label]) grouped[label] = { name: label, count: 0, value: 0 };
      grouped[label].count += 1;
      grouped[label].value = grouped[label].count;
    });
    return topEntries(grouped, limit || 5).map(function (item) { return { name: item.name, count: item.count || item.value || 0 }; });
  }

  function formatNamedTotals(items, totalLabel) {
    return (items || []).map(function (item) {
      return item.name + " (count " + chartNumber(item.count || 0) + (item.total != null ? ", " + totalLabel + " " + chartNumber(item.total) + ", avg " + chartNumber(item.average || 0) : "") + ")";
    }).join("; ");
  }

  function localDeepNarrative(metadata) {
    metadata = metadata || {};
    var rows = rowsForAnalytics(metadata);
    var primaryMetric = canonicalPrimaryMetricColumn(metadata);
    var department = findColumnByWords(metadata, ["department", "dept"]);
    var city = findColumnByWords(metadata, ["city", "location"]);
    var diagnosis = findColumnByWords(metadata, ["diagnosis", "condition"]);
    var procedure = findColumnByWords(metadata, ["procedure", "treatment"]);
    var admissionType = findColumnByWords(metadata, ["admissiontype", "admission type"]);
    var bedType = findColumnByWords(metadata, ["bedtype", "bed type"]);
    var outcome = findColumnByWords(metadata, ["outcome", "patient outcome"]);
    var claimStatus = findColumnByWords(metadata, ["claimstatus", "claim status"]);
    var los = findColumnByWords(metadata, ["lengthofstaydays", "length of stay", "los"]);
    var satisfaction = findColumnByWords(metadata, ["satisfactionscore", "satisfaction score"]);
    var wait = findColumnByWords(metadata, ["waittimeminutes", "wait time"]);
    var trends = [];
    var anomalies = [];
    var segments = [];
    var executiveSummary = [];
    var keyDrivers = [];
    var riskFlags = [];
    var opportunityFinder = opportunityItems(metadata);
    var metricGlossary = metricGlossaryItems(metadata);
    var confidenceAssumptions = confidenceAssumptionItems(metadata);
    var followUpQuestions = [];
    var rowCount = metadata.rowCount || rows.length || 0;
    var missingTotal = Object.keys(metadata.missingValues || {}).reduce(function (sum, key) { return sum + (Number(metadata.missingValues[key]) || 0); }, 0);
    if (primaryMetric) {
      var stats = metadata.numericStats && metadata.numericStats[primaryMetric] || {};
      executiveSummary.push(columnLabel(primaryMetric) + " is the primary business metric with total " + chartNumber(stats.sum || 0) + " across " + rowCount + " parsed rows.");
      trends.push(columnLabel(primaryMetric) + " is the primary financial metric for this healthcare sheet; total is " + chartNumber(stats.sum || 0) + " across " + rowCount + " parsed rows.");
      if (department) {
        var topDepartment = topGroupByMetric(metadata, department, primaryMetric);
        if (topDepartment) {
          executiveSummary.push("Top department by " + columnLabel(primaryMetric) + " is " + topDepartment.name + " with " + chartNumber(topDepartment.total) + " across " + topDepartment.count + " rows.");
          trends.push("Department-level billing is concentrated most in " + topDepartment.name + " with " + chartNumber(topDepartment.total) + " " + columnLabel(primaryMetric) + " across " + topDepartment.count + " rows.");
          keyDrivers.push("Department driver: " + topDepartment.name + " contributes the highest " + columnLabel(primaryMetric) + " total.");
        }
      }
      if (diagnosis) {
        var topDiagnosis = topGroupByMetric(metadata, diagnosis, primaryMetric);
        if (topDiagnosis) keyDrivers.push("Diagnosis driver: " + topDiagnosis.name + " is the highest diagnosis by " + columnLabel(primaryMetric) + ".");
      }
      if (procedure) {
        var topProcedure = topGroupByMetric(metadata, procedure, primaryMetric);
        if (topProcedure) keyDrivers.push("Procedure driver: " + topProcedure.name + " is the highest procedure by " + columnLabel(primaryMetric) + ".");
      }
      if (outcome) {
        var topOutcome = topGroupByMetric(metadata, outcome, primaryMetric);
        if (topOutcome) keyDrivers.push("Outcome driver: " + topOutcome.name + " carries the largest " + columnLabel(primaryMetric) + " total.");
      }
      if (stats.max) anomalies.push("High-value " + columnLabel(primaryMetric) + " outliers exist; maximum row value is " + chartNumber(stats.max) + ". Validate billing accuracy and clinical justification before reporting.");
      if (stats.max) riskFlags.push("High-value " + columnLabel(primaryMetric) + " outliers may distort totals and averages.");
    }
    if (missingTotal) {
      executiveSummary.push("Data quality needs review: " + missingTotal + " missing values and " + (metadata.duplicateRows || 0) + " duplicate rows were detected.");
      riskFlags.push("Missing values may bias segment-level reporting; review the highest-missing columns before publishing.");
    }
    if (metadata.duplicateRows) riskFlags.push("Duplicate rows can inflate financial totals; confirm deduplication rules before final reporting.");
    if (los && metadata.numericStats && metadata.numericStats[los] && metadata.numericStats[los].max) {
      anomalies.push("Length of stay has extreme values up to " + chartNumber(metadata.numericStats[los].max) + " days; review long-stay encounters for clinical or data-entry causes.");
      riskFlags.push("Long-stay outliers may indicate unusual cases or data-entry issues.");
    }
    if (satisfaction) trends.push("Average satisfaction score is " + chartNumber((metadata.numericStats[satisfaction] || {}).average || 0) + "; review it with wait time and outcomes rather than as a standalone KPI.");
    if (wait) trends.push("Average wait time is " + chartNumber((metadata.numericStats[wait] || {}).average || 0) + " minutes; compare high-wait departments with encounter volume and outcomes.");
    if (claimStatus && metadata.missingValues && Number(metadata.missingValues[claimStatus] || 0)) {
      anomalies.push("ClaimStatus has " + Number(metadata.missingValues[claimStatus] || 0) + " blank records; standardize blanks before claim workflow reporting.");
      riskFlags.push("Blank ClaimStatus records can weaken workflow and approval-status reporting.");
    }
    if (city && primaryMetric) segments.push("City-level " + columnLabel(primaryMetric) + ": " + formatNamedTotals(groupSummaryByMetric(metadata, city, primaryMetric, 5), columnLabel(primaryMetric)) + ".");
    if (department) segments.push("Department volume: " + groupCountSummary(metadata, department, 5).map(function (item) { return item.name + " " + chartNumber(item.count); }).join("; ") + ".");
    if (procedure) segments.push("Procedure volume: " + groupCountSummary(metadata, procedure, 5).map(function (item) { return item.name + " " + chartNumber(item.count); }).join("; ") + ".");
    if (admissionType) segments.push("Admission type mix: " + groupCountSummary(metadata, admissionType, 5).map(function (item) { return item.name + " " + chartNumber(item.count); }).join("; ") + ".");
    if (bedType) segments.push("Bed type mix: " + groupCountSummary(metadata, bedType, 5).map(function (item) { return item.name + " " + chartNumber(item.count); }).join("; ") + ".");
    if (outcome) segments.push("Outcome distribution: " + groupCountSummary(metadata, outcome, 5).map(function (item) { return item.name + " " + chartNumber(item.count); }).join("; ") + ".");
    if (department && primaryMetric) followUpQuestions.push("Show " + columnLabel(primaryMetric) + " by " + columnLabel(department) + " and " + (procedure ? columnLabel(procedure) : "top category") + ".");
    if (primaryMetric) followUpQuestions.push("Find the top high-value " + columnLabel(primaryMetric) + " outlier rows with supporting columns.");
    if (wait && satisfaction && department) followUpQuestions.push("Compare average wait time and satisfaction score by " + columnLabel(department) + ".");
    if (claimStatus) followUpQuestions.push("Show ClaimStatus distribution with Blank records separated.");
    if (los && department) followUpQuestions.push("Show average length of stay by " + columnLabel(department) + " and outcome.");
    return {
      executiveSummary: executiveSummary,
      keyDrivers: keyDrivers,
      riskFlags: riskFlags,
      opportunityFinder: opportunityFinder,
      metricGlossary: metricGlossary,
      confidenceAssumptions: confidenceAssumptions,
      followUpQuestions: followUpQuestions,
      trends: trends,
      anomalies: anomalies,
      segmentBreakdowns: segments
    };
  }

  function polishAnalysisNarrativeItems(items) {
    return arrayFrom(items).map(function (item) {
      var text = plainItem(item);
      text = text.replace(/\bSegment:\s*/gi, "");
      text = text.replace(/\bDetails:\s*/gi, "");
      text = text.replace(/\bName:\s*/gi, "");
      text = text.replace(/\bData:\s*/gi, "");
      text = text.replace(/\s*;\s*/g, "; ");
      text = text.replace(/\s{2,}/g, " ").trim();
      return text;
    }).filter(Boolean);
  }

  function suppressSecondaryClaimMetricNarrative(metadata, item) {
    metadata = metadata || {};
    var profile = analyticsSemanticProfile(metadata);
    if (profile.domain !== "healthcare") return false;
    var primaryMetric = canonicalPrimaryMetricColumn(metadata);
    if (!/totalbill/i.test(columnKey(primaryMetric))) return false;
    var text = plainItem(item).toLowerCase();
    if (!text) return false;
    if (!/(insuranceclaim_inr|insurance\s+claim|insurance\s+claims|claim\s+amount|claimed\s+amount|over-claimed|under-claimed)/i.test(text)) return false;
    return !/\bclaimstatus\b|\bclaim status\b|\bpending\b|\brejected\b|\bapproved\b|\bblank\b/i.test(text);
  }

  function cleanNarrativeList(metadata, items, limit) {
    return mergeUnique(polishAnalysisNarrativeItems(items).filter(function (item) {
      return !analysisItemLooksBlank(item) && !suppressSecondaryClaimMetricNarrative(metadata, item);
    }), limit);
  }

  function deterministicAnalysisRepair(metadata, analysis, deep) {
    metadata = metadata || {};
    analysis = Object.assign({}, analysis || {});
    var deterministic = localAnalysis(metadata);
    var narrative = deep ? localDeepNarrative(metadata) : {};
    analysis.analysisMode = deep ? "deep" : "normal";
    analysis.summary = deterministic.summary;
    analysis.datasetReadiness = deterministic.datasetReadiness;
    analysis.topFindings = deterministic.topFindings;
    analysis.dataQualityImpact = deterministic.dataQualityImpact;
    analysis.missingFieldExpectations = deterministic.missingFieldExpectations;
    analysis.suggestedNextQuestions = deterministic.suggestedNextQuestions;
    analysis.dataQualityIssues = deterministic.dataQualityIssues;
    analysis.executiveKPIs = deterministic.executiveKPIs;
    analysis.analytics = deterministic.analytics;
    analysis.recommendedCharts = smartRecommendedCharts(metadata, deep);
    analysis.recommendedActions = cleanNarrativeList(metadata, (deterministic.recommendedActions || []).concat(arrayFrom(analysis.recommendedActions)), deep ? 10 : 6);
    if (deep) {
      analysis.executiveSummary = cleanNarrativeList(metadata, (narrative.executiveSummary || []).concat(arrayFrom(analysis.executiveSummary)), 6);
      analysis.keyDrivers = cleanNarrativeList(metadata, (narrative.keyDrivers || []).concat(arrayFrom(analysis.keyDrivers)), 8);
      analysis.riskFlags = cleanNarrativeList(metadata, (narrative.riskFlags || []).concat(arrayFrom(analysis.riskFlags)), 8);
      analysis.opportunityFinder = cleanNarrativeList(metadata, (narrative.opportunityFinder || []).concat(arrayFrom(analysis.opportunityFinder)), 8);
      analysis.metricGlossary = cleanNarrativeList(metadata, (narrative.metricGlossary || []).concat(arrayFrom(analysis.metricGlossary)), 8);
      analysis.confidenceAssumptions = cleanNarrativeList(metadata, (narrative.confidenceAssumptions || []).concat(arrayFrom(analysis.confidenceAssumptions)), 8);
      analysis.followUpQuestions = cleanNarrativeList(metadata, (narrative.followUpQuestions || []).concat(arrayFrom(analysis.followUpQuestions)), 8);
      analysis.trends = cleanNarrativeList(metadata, (narrative.trends || []).concat(arrayFrom(analysis.trends)), 8);
      analysis.anomalies = cleanNarrativeList(metadata, (narrative.anomalies || []).concat(arrayFrom(analysis.anomalies)), 8);
      analysis.segmentBreakdowns = cleanNarrativeList(metadata, narrative.segmentBreakdowns && narrative.segmentBreakdowns.length ? narrative.segmentBreakdowns : arrayFrom(analysis.segmentBreakdowns), 8);
    } else {
      analysis.executiveSummary = [];
      analysis.keyDrivers = [];
      analysis.riskFlags = [];
      analysis.opportunityFinder = [];
      analysis.metricGlossary = [];
      analysis.confidenceAssumptions = [];
      analysis.followUpQuestions = [];
      analysis.trends = [];
      analysis.anomalies = [];
      analysis.segmentBreakdowns = [];
    }
    return analysis;
  }

  function metadataForAi(metadata, deep) {
    var out = Object.assign({}, metadata || {});
    var semanticProfile = analyticsSemanticProfile(metadata || {});
    var primaryMetric = canonicalPrimaryMetricColumn(metadata || {}) || semanticProfile.primaryMetric;
    delete out._localRows;
    delete out.sampleRows;
    delete out.draftRows;
    out.analysisMode = deep ? "deep_full_dataset_analytics" : "normal_full_dataset_analytics";
    out.semanticProfile = {
      domain: semanticProfile.domain,
      confidence: semanticProfile.confidence,
      primaryMetric: primaryMetric,
      primaryDimension: semanticProfile.primaryDimension,
      avoidDefaultRule: "Do not use identifiers, free-text, Age, Gender, or other demographics as default metrics/dimensions unless explicitly requested."
    };
    out.fullDatasetAnalytics = buildAdvancedAnalytics(metadata || {});
    if (deep) out.advancedAnalytics = out.fullDatasetAnalytics;
    else delete out.advancedAnalytics;
    return out;
  }

  function arrayFrom(value) {
    return Array.isArray(value) ? value : (value ? [value] : []);
  }

  function mergeUnique(items, limit) {
    var seen = {};
    var out = [];
    arrayFrom(items).forEach(function (item) {
      var key = typeof item === "string" ? item : JSON.stringify(item);
      if (!seen[key]) {
        seen[key] = true;
        out.push(item);
      }
    });
    return limit ? out.slice(0, limit) : out;
  }

  function chunkMetadata(metadata, rows, start, totalRows, index, totalChunks) {
    var out = Object.assign({}, metadata || {});
    delete out.sampleRows;
    out.draftRows = rows;
    out.rowCount = rows.length;
    out.totalWorkbookRows = totalRows;
    out.analysisChunk = {
      index: index + 1,
      total: totalChunks,
      startRow: start + 1,
      endRow: start + rows.length,
      rowCount: rows.length,
      totalRows: totalRows
    };
    return out;
  }

  function combineAnalysisPayloads(metadata, payloads, chunks) {
    var first = payloads[0] || localAnalysisPayload(metadata || {}, !!(payload && payload.deep_analysis));
    var analysis = Object.assign({}, first.analysis || {});
    var summary = [];
    var dataQualityIssues = [];
    var formattingSuggestions = [];
    var analytics = [];
    var recommendedCharts = [];
    payloads.forEach(function (payload) {
      var item = payload && payload.analysis || {};
      summary = summary.concat(arrayFrom(item.summary));
      dataQualityIssues = dataQualityIssues.concat(arrayFrom(item.dataQualityIssues));
      formattingSuggestions = formattingSuggestions.concat(arrayFrom(item.formattingSuggestions));
      analytics = analytics.concat(arrayFrom(item.analytics));
      recommendedCharts = recommendedCharts.concat(arrayFrom(item.recommendedCharts));
    });
    if (chunks && chunks.length) {
      summary.unshift("Analysis coverage: " + chunks.map(function (chunk) {
        return "pass " + chunk.index + " checked rows " + chunk.startRow + "-" + chunk.endRow + " (" + chunk.rowCount + " rows)";
      }).join("; ") + ".");
    }
    analysis.summary = mergeUnique(summary, 20);
    analysis.dataQualityIssues = mergeUnique(dataQualityIssues, 16);
    analysis.formattingSuggestions = mergeUnique(formattingSuggestions, 16);
    analysis.analytics = mergeUnique(analytics, 20);
    analysis.recommendedCharts = mergeUnique(recommendedCharts, 10);
    return Object.assign({}, first, {
      analysis: analysis,
      analysis_chunks: chunks,
      blocks: []
    });
  }

  function payloadAnswerText(payload) {
    payload = payload || {};
    var analysis = payload.analysis || {};
    return payload.answer || payload.response || payload.text || sentenceList(analysis.summary || analysis.analytics || payload.summary || payload.analytics, "");
  }

  function splitCSV(value) {
    return String(value || "").split(",").map(function (item) { return item.trim(); }).filter(Boolean);
  }

  function combineFollowupPayloads(payloads, chunks) {
    var first = payloads[0] || {};
    var blocks = [];
    var answers = [];
    payloads.forEach(function (payload, index) {
      payload = payload || {};
      var chunk = chunks[index] || {};
      var chunkBlocks = payload.blocks || payload.editorBlocks || payload.answerBlocks || payload.createdBlocks || (payload.data && payload.data.blocks);
      if (Array.isArray(chunkBlocks)) {
        blocks = blocks.concat(chunkBlocks);
      }
      var answer = payloadAnswerText(payload);
      if (answer) {
        answers.push("Pass " + (chunk.index || index + 1) + " rows " + (chunk.startRow || "?") + "-" + (chunk.endRow || "?") + ": " + answer);
      }
    });
    var coverage = chunks.length ? "Manual analysis coverage: " + chunks.map(function (chunk) {
      return "pass " + chunk.index + " checked rows " + chunk.startRow + "-" + chunk.endRow + " (" + chunk.rowCount + " rows)";
    }).join("; ") + "." : "";
    return Object.assign({}, first, {
      answer: [coverage].concat(answers).filter(Boolean).join("\n\n"),
      blocks: blocks,
      analysis_chunks: chunks
    });
  }

  function analyzeWithFallback(payload) {
    return requestWithRetry("analyze", payload).catch(function (error) {
      if (!isRetryableSheetError(error)) throw error;
      return Object.assign(localAnalysisPayload(payload.metadata || {}, !!payload.deep_analysis), {
        warning: error.message || "AI response was unavailable, so a local analysis was generated."
      });
    });
  }

  function analyzeSheetInChunks(payload, onChunk) {
    payload = Object.assign({}, payload || {});
    var metadata = payload.metadata || {};
    var rows = rowsForAnalytics(metadata);
    var analysisPrompt = payload.deep_analysis ? "Deep Analyze uploaded spreadsheet with full-dataset compact analytics and create editor blocks." : "Analyze uploaded spreadsheet with full-dataset compact analytics and create editor blocks.";
    var modeContract = promptModeContract(analysisPrompt);
    if (typeof onChunk === "function") {
      onChunk({
        index: 1,
        total: 1,
        startRow: rows.length ? 1 : 0,
        endRow: rows.length || metadata.rowCount || 0,
        rowCount: rows.length || metadata.rowCount || 0,
        localAnalytics: !!payload.deep_analysis
      });
    }
    return fetchQualityRules("sheet_analyzer", analysisPrompt).then(function (qualityRules) {
      var instructionText = [qualityRulesText(qualityRules), promptModeInstructions(modeContract)].filter(Boolean).join("\n\n");
      return analyzeWithFallback(Object.assign({}, payload, {
        metadata: metadataForAi(metadata, !!payload.deep_analysis),
        rows: undefined,
        full_rows: undefined,
        analysis_chunk: null,
        prompt_modes: modeContract,
        quality_rules: qualityRules,
        quality_instructions: instructionText
      })).then(function (result) {
        logQualityEvent({
          feature: "sheet_analyzer",
          status: result && result.warning ? "fallback" : "passed",
          route: payload.deep_analysis ? "deep_analyze" : "analyze",
          prompt: analysisPrompt,
          contract: {
            feature: "sheet_analyzer",
            route: payload.deep_analysis ? "deep_analyze" : "analyze",
            create_blocks: !!payload.create_blocks,
            deep_analysis: !!payload.deep_analysis,
            rows: rows.length || metadata.rowCount || 0,
            columns: metadata.columnCount || (metadata.headings || []).length || 0
          },
          validation: {
            ok: true,
            warning: result && result.warning ? result.warning : ""
          },
          metadata: {
            file_id: payload.file_id || null,
            source: "main_analysis",
            prompt_modes: modeContract.modes
          }
        });
      var repairedAnalysis = deterministicAnalysisRepair(metadata, result && result.analysis || {}, !!payload.deep_analysis);
      var repairedBlocks = generatedNestedBlocks(metadata, repairedAnalysis);
      var overviewContract = {
        intentType: payload.deep_analysis ? "deep_overview" : "normal_overview",
        aggregation: "deterministic_dataset_profile",
        rowLevelAllowed: false,
        requiresTable: true,
        requiresChart: true,
        groupBy: safeDefaultDimensionColumn(metadata),
        metric: safeDefaultMetricColumn(metadata),
        deterministicRoute: "generatedNestedBlocks"
      };
      var overviewPlan = {
        version: analyticsEngineVersion(),
        intentType: overviewContract.intentType,
        deterministicRoute: overviewContract.deterministicRoute,
        chartType: "auto",
        dimensions: overviewContract.groupBy ? [overviewContract.groupBy] : [],
        metrics: overviewContract.metric ? [overviewContract.metric] : [],
        formulas: [],
        aggregation: overviewContract.aggregation,
        filters: [],
        sort: {},
        limit: null,
        dateGranularity: "",
        rowLevelAllowed: false,
        forbiddenColumns: [],
        identifiers: identifierColumns(metadata),
        contract: overviewContract,
        domain: analyticsSemanticProfile(metadata).domain,
        schemaProfile: analyticsSchemaProfile(metadata),
        routeCapabilities: deterministicRouteCapabilities(metadata)
      };
      var overviewValidation = runAnalyticsValidators({ metadata: metadata, question: analysisPrompt, plan: overviewPlan, blocks: repairedBlocks });
      var overviewResult = makeExecutionResult(metadata, analysisPrompt, overviewPlan, repairedBlocks, overviewValidation, validateExecutionPlan(metadata, analysisPrompt, overviewPlan));
      return Object.assign({}, result, {
        analysis: repairedAnalysis,
        blocks: repairedBlocks,
        intent_contract: overviewContract,
        execution_plan: overviewPlan,
        execution_result: overviewResult,
        analysis_coverage: payload.deep_analysis ? {
          mode: "full_dataset_compact_analytics",
          rows_analyzed_locally: rows.length || metadata.rowCount || 0,
          raw_rows_sent_to_ai: false
        } : {
          mode: "normal_full_dataset_compact_analytics",
          rows_analyzed_locally: rows.length || metadata.rowCount || 0,
          raw_rows_sent_to_ai: false
        }
      });
      });
    });
  }

  function followupInChunks(payload, onChunk) {
    payload = Object.assign({}, payload || {});
    var metadata = payload.metadata || {};
    var rows = rowsForAnalytics(metadata);
    var validationRepair = !!payload.ai_validation_loop;
    if (!rows.length || rows.length <= ANALYSIS_CHUNK_SIZE) {
      var singleRows = rows.slice(0, ANALYSIS_CHUNK_SIZE);
      return requestWithRetry("followup", Object.assign({}, payload, {
        metadata: singleRows.length ? chunkMetadata(metadata, singleRows, 0, rows.length || metadata.rowCount || 0, 0, 1) : metadata,
        rows: singleRows,
        full_rows: singleRows,
        analysis_chunk: singleRows.length ? { index: 1, total: 1, start_row: 1, end_row: singleRows.length, row_count: singleRows.length, total_rows: rows.length || metadata.rowCount || 0 } : null,
        billing_mode: validationRepair ? "validation_repair" : "single_manual_question",
        defer_billing_completion: !validationRepair,
        charge_on_chunk: !validationRepair,
        credits_required: validationRepair ? undefined : payload.credits_required
      }));
    }
    var chunks = [];
    for (var start = 0; start < rows.length; start += ANALYSIS_CHUNK_SIZE) {
      chunks.push({
        index: chunks.length + 1,
        start: start,
        rows: rows.slice(start, start + ANALYSIS_CHUNK_SIZE)
      });
    }
    var outputs = [];
    return chunks.reduce(function (chain, chunk) {
      return chain.then(function () {
        var info = {
          index: chunk.index,
          total: chunks.length,
          startRow: chunk.start + 1,
          endRow: chunk.start + chunk.rows.length,
          rowCount: chunk.rows.length
        };
        if (typeof onChunk === "function") onChunk(info);
        return requestWithRetry("followup", Object.assign({}, payload, {
          metadata: chunkMetadata(metadata, chunk.rows, chunk.start, rows.length, chunk.index - 1, chunks.length),
          rows: chunk.rows,
          full_rows: chunk.rows,
          analysis_chunk: {
            index: info.index,
            total: info.total,
            start_row: info.startRow,
            end_row: info.endRow,
            row_count: info.rowCount,
            total_rows: rows.length
          },
          billing_mode: validationRepair ? "validation_repair" : "single_manual_question",
          defer_billing_completion: !validationRepair && chunk.index === 1,
          charge_on_chunk: validationRepair ? false : chunk.index === 1,
          credits_required: validationRepair ? undefined : (chunk.index === 1 ? payload.credits_required : undefined)
        })).then(function (result) {
          outputs.push(result);
        });
      });
    }, Promise.resolve()).then(function () {
      return combineFollowupPayloads(outputs, chunks.map(function (chunk) {
        return { index: chunk.index, startRow: chunk.start + 1, endRow: chunk.start + chunk.rows.length, rowCount: chunk.rows.length };
      }));
    });
  }

  function parseCsv(text) {
    var rows = [], row = [], value = "", quote = false;
    for (var index = 0; index < text.length; index += 1) {
      var ch = text[index];
      if (quote) {
        if (ch === '"' && text[index + 1] === '"') { value += '"'; index += 1; }
        else if (ch === '"') quote = false;
        else value += ch;
      } else if (ch === '"') quote = true;
      else if (ch === ",") { row.push(value); value = ""; }
      else if (ch === "\n") { row.push(value); rows.push(row); row = []; value = ""; }
      else if (ch !== "\r") value += ch;
    }
    row.push(value);
    if (row.some(function (cell) { return String(cell).trim() !== ""; })) rows.push(row);
    return rows;
  }

  function xmlText(value) {
    return String(value || "").replace(/<[^>]+>/g, "").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&apos;/g, "'");
  }

  function unzipXlsx(buffer) {
    if (!("DecompressionStream" in global)) {
      return Promise.reject(new Error("This browser cannot parse XLSX locally. Please upload CSV or use a Chromium-based browser."));
    }
    var bytes = new Uint8Array(buffer), files = [], offset = 0;
    while (offset + 30 < bytes.length) {
      var sig = bytes[offset] | bytes[offset + 1] << 8 | bytes[offset + 2] << 16 | bytes[offset + 3] << 24;
      if (sig !== 0x04034b50) break;
      var method = bytes[offset + 8] | bytes[offset + 9] << 8;
      var compressed = bytes[offset + 18] | bytes[offset + 19] << 8 | bytes[offset + 20] << 16 | bytes[offset + 21] << 24;
      var size = bytes[offset + 22] | bytes[offset + 23] << 8 | bytes[offset + 24] << 16 | bytes[offset + 25] << 24;
      var nameLen = bytes[offset + 26] | bytes[offset + 27] << 8;
      var extraLen = bytes[offset + 28] | bytes[offset + 29] << 8;
      var name = new TextDecoder().decode(bytes.slice(offset + 30, offset + 30 + nameLen));
      var start = offset + 30 + nameLen + extraLen;
      var data = bytes.slice(start, start + compressed);
      files.push({ name: name, method: method, size: size, data: data });
      offset = start + compressed;
    }
    return Promise.all(files.map(function (file) {
      if (file.method === 0) return Promise.resolve([file.name, new TextDecoder().decode(file.data)]);
      var stream = new Blob([file.data]).stream().pipeThrough(new DecompressionStream("deflate-raw"));
      return new Response(stream).arrayBuffer().then(function (out) {
        return [file.name, new TextDecoder().decode(out)];
      });
    })).then(function (items) {
      var map = {};
      items.forEach(function (item) { map[item[0]] = item[1]; });
      return map;
    });
  }

  function parseXlsx(file) {
    return file.arrayBuffer().then(unzipXlsx).then(function (zip) {
      var shared = [];
      String(zip["xl/sharedStrings.xml"] || "").replace(/<si[\s\S]*?<\/si>/g, function (si) {
        shared.push(xmlText(si));
        return si;
      });
      var sheet = zip["xl/worksheets/sheet1.xml"] || Object.keys(zip).filter(function (name) { return /^xl\/worksheets\/sheet\d+\.xml$/.test(name); }).map(function (name) { return zip[name]; })[0] || "";
      var rows = [];
      String(sheet).replace(/<row[\s\S]*?<\/row>/g, function (rowXml) {
        var row = [];
        rowXml.replace(/<c([^>]*)>([\s\S]*?)<\/c>/g, function (_, attrs, cellXml) {
          var ref = (attrs.match(/\sr="([A-Z]+)\d+"/) || [])[1] || "";
          var col = ref ? ref.split("").reduce(function (sum, letter) { return sum * 26 + letter.charCodeAt(0) - 64; }, 0) - 1 : row.length;
          var type = (attrs.match(/\st="([^"]+)"/) || [])[1] || "";
          var raw = xmlText((cellXml.match(/<v[^>]*>([\s\S]*?)<\/v>/) || cellXml.match(/<t[^>]*>([\s\S]*?)<\/t>/) || [])[1] || "");
          row[col] = type === "s" ? shared[Number(raw)] || "" : raw;
          return _;
        });
        if (row.some(function (cell) { return String(cell || "").trim() !== ""; })) rows.push(row);
        return rowXml;
      });
      return rows;
    });
  }

  function inferType(values) {
    var nonEmpty = values.filter(function (value) { return String(value).trim() !== ""; });
    if (!nonEmpty.length) return "text";
    var tests = {
      boolean: nonEmpty.filter(function (v) { return /^(true|false|yes|no|0|1)$/i.test(String(v).trim()); }).length,
      percentage: nonEmpty.filter(function (v) { return /^-?\d+(?:\.\d+)?%$/.test(String(v).trim()); }).length,
      currency: nonEmpty.filter(function (v) { return /^[₹$€£]?\s*-?\d[\d,]*(?:\.\d+)?$/.test(String(v).trim()) && /[₹$€£,]/.test(String(v)); }).length,
      number: nonEmpty.filter(function (v) { return /^-?\d[\d,]*(?:\.\d+)?$/.test(String(v).trim()); }).length,
      date: nonEmpty.filter(function (v) { return !Number.isNaN(Date.parse(String(v).trim())) && /[-/A-Za-z]/.test(String(v)); }).length
    };
    var threshold = Math.max(1, Math.ceil(nonEmpty.length * 0.7));
    return tests.currency >= threshold ? "currency" : tests.percentage >= threshold ? "percentage" : tests.number >= threshold ? "number" : tests.date >= threshold ? "date" : tests.boolean >= threshold ? "boolean" : "text";
  }

  function numericValue(value) {
    var raw = String(value == null ? "" : value).trim();
    if (raw === "") return null;
    var cleaned = raw.replace(/[₹$€£,%\s]/g, "").replace(/,/g, "");
    if (cleaned === "" || !/^-?\d+(?:\.\d+)?$/.test(cleaned)) return null;
    var n = Number(cleaned);
    return Number.isFinite(n) ? n : null;
  }

  function truthySheetValue(value) {
    var text = String(value == null ? "" : value).trim().toLowerCase();
    if (!text) return false;
    if (/^(1|true|yes|y|readmitted|readmission|positive|high)$/i.test(text)) return true;
    var number = numericValue(text);
    return number !== null && number > 0;
  }

  function roundNumber(value) {
    value = Number(value);
    return Number.isFinite(value) ? Math.round(value * 100) / 100 : 0;
  }

  function percentile(sorted, p) {
    if (!sorted.length) return 0;
    var index = (sorted.length - 1) * p;
    var lower = Math.floor(index);
    var upper = Math.ceil(index);
    if (lower === upper) return sorted[lower];
    return sorted[lower] + (sorted[upper] - sorted[lower]) * (index - lower);
  }

  function topEntries(map, limit, ascending) {
    return Object.keys(map || {}).map(function (key) {
      return Object.assign({ name: key }, map[key] && typeof map[key] === "object" ? map[key] : { value: map[key] });
    }).sort(function (a, b) {
      var av = Number(a.value != null ? a.value : a.total || a.revenue || a.profit || a.count || 0);
      var bv = Number(b.value != null ? b.value : b.total || b.revenue || b.profit || b.count || 0);
      return ascending ? av - bv : bv - av;
    }).slice(0, limit || 10);
  }

  function columnAliases(headings) {
    var aliases = {
      orderId: ["orderid", "order_id", "order", "invoiceid", "invoice"],
      date: ["date", "orderdate", "createdat", "created"],
      customer: ["customer", "customername", "client", "buyer"],
      category: ["category", "productcategory", "department"],
      product: ["product", "productname", "item", "sku", "itemname"],
      quantity: ["quantity", "qty", "units", "unitssold"],
      unitPrice: ["unitprice", "price", "sellingprice", "rate"],
      revenue: ["revenue", "sales", "amount", "total", "totalrevenue", "ordertotal"],
      discount: ["discount", "discountamount", "discountrate"],
      profit: ["profit", "netprofit", "marginamount"],
      city: ["city", "town"],
      country: ["country", "nation"],
      paymentMethod: ["paymentmethod", "payment", "paymenttype", "method"]
    };
    var resolved = {};
    Object.keys(aliases).forEach(function (key) {
      var match = headings.filter(function (heading) {
        var normalized = columnKey(heading);
        return aliases[key].some(function (alias) { return normalized === alias || normalized.indexOf(alias) >= 0; });
      })[0];
      if (match) resolved[key] = match;
    });
    return resolved;
  }

  function columnMeta(metadata, column) {
    return ((metadata && metadata.columns) || []).filter(function (item) { return item.name === column; })[0] || {};
  }

  function semanticColumnInfo(metadata, column) {
    metadata = metadata || {};
    var key = columnKey(column);
    var meta = columnMeta(metadata, column);
    var unique = Number(meta.unique || 0);
    var rows = Number(metadata.rowCount || (rowsForAnalytics(metadata) || []).length || 0);
    var monetary = /revenue|sales|amount|bill|billing|charge|cost|claim|payment|price|profit|expense|premium|balance|salary|bonus|commission|compensation|totalcomp|compinr|payroll|wage|spend|shipping|freight|postage|fee|tuition|scholarship|grant|funding|inr$|usd$|eur$|gbp$|amount$/i.test(key);
    var quantity = /quantity|qty|units|count|volume|stock|inventory/i.test(key);
    var operational = /wait|stay|duration|leadtime|score|rating|satisfaction|time|days|hours|minutes|outstanding|risk|usage|consumption|consume|kwh|kw|demand|outage|utilization|utilisation|attendance|enrollment|enrolment|headcount|capacity|occupancy|volume|distance|fuel|litres|liters|weight|impressions|clicks|conversions|views|sessions|tickets|cases|incidents|visits|matches|points|shots|fouls|possession|accuracy|^metric[a-z0-9]*$|^value[a-z0-9]*$/i.test(key);
    var percentage = /pct|percent|percentage|rate|ratio|share/i.test(key) || /%/.test(String(column || ""));
    var status = !monetary && !quantity && !percentage && /status|outcome|flag|yesno|^(approved|rejected|passed|failed)$/i.test(key);
    var categoricalName = status || /gender|sex|type|category|department|diagnosis|procedure|provider|supplier|vendor|city|region|country|name|description|comment/i.test(key);
    var isNumeric = Object.prototype.hasOwnProperty.call(metadata.numericStats || {}, column) && meta.type === "number" && (!categoricalName || monetary || quantity || operational || percentage);
    var isCategory = (metadata.categoricalColumns || []).indexOf(column) >= 0 || meta.type === "text" || meta.type === "boolean";
    var isDate = (metadata.dateColumns || []).indexOf(column) >= 0 || meta.type === "date";
    var businessMeasure = isNumeric && (monetary || quantity || operational || percentage);
    var identifier = !businessMeasure && (/(^|_)?id$|id$|uuid|guid|code|number|no$/i.test(key) || (rows > 50 && unique / Math.max(1, rows) > 0.92 && !/status|type|category|department|city|region|country/i.test(key)));
    var demographic = /^(age|gender|sex|race|ethnicity|dob|dateofbirth)$/.test(key);
    var freeText = /name|email|phone|address|comment|notes?|description|remarks?|message|text/i.test(key) && !/drugname|productname|itemname/i.test(key);
    var avoidDefault = identifier || demographic || freeText || (isCategory && rows > 50 && unique / Math.max(1, rows) > 0.35);
    var role = identifier ? "identifier" :
      demographic && isNumeric ? "demographic_measure" :
      demographic ? "demographic_dimension" :
      isDate ? "date" :
      isNumeric && percentage ? "percentage" :
      isNumeric && monetary ? "measure_sum" :
      isNumeric && quantity ? "measure_sum" :
      isNumeric && operational ? "measure_avg" :
      isNumeric ? "measure" :
      status ? "status" :
      isCategory ? "dimension" : "free_text";
    return {
      name: column,
      key: key,
      role: role,
      isNumeric: isNumeric,
      isCategory: isCategory,
      isDate: isDate,
      identifier: identifier,
      demographic: demographic,
      percentage: percentage,
      avoidDefault: avoidDefault,
      unique: unique,
      priority: 0
    };
  }

  function detectDatasetDomain(metadata) {
    var text = ((metadata && metadata.headings) || []).join(" ").toLowerCase();
    var domains = [
      { name: "healthcare", terms: ["patient", "encounter", "admission", "discharge", "diagnosis", "procedure", "department", "doctor", "readmission", "icu", "outcome", "insurance", "claim", "bedtype", "totalbill"] },
      { name: "pharma", terms: ["drug", "medicine", "dosage", "manufacturer", "batch", "qa", "recalled", "supplier", "pharmacy"] },
      { name: "ecommerce", terms: ["order", "product", "cart", "customer", "payment", "discount", "revenue", "profit", "quantity", "saleschannel"] },
      { name: "hr", terms: ["employee", "salary", "department", "attrition", "hire", "performance", "manager"] },
      { name: "finance", terms: ["account", "balance", "transaction", "debit", "credit", "expense", "income", "profit", "loss"] },
      { name: "logistics", terms: ["shipment", "delivery", "warehouse", "inventory", "stock", "freight", "carrier", "route"] },
      { name: "marketing", terms: ["campaign", "channel", "impressions", "clicks", "leads", "conversion", "spend"] },
      { name: "manufacturing", terms: ["production", "defect", "machine", "plant", "batch", "yield", "downtime"] },
      { name: "education", terms: ["student", "course", "grade", "teacher", "attendance", "enrollment"] },
      { name: "legal_case_management", terms: ["case", "caseid", "filed", "fileddate", "casetype", "court", "jurisdiction", "lawyer", "casestatus", "legalcost", "claimvalue", "hearing", "settlement", "settlementamount"] },
      { name: "insurance", terms: ["policy", "claim", "premium", "coverage", "provider", "insured"] }
    ];
    var scored = domains.map(function (domain) {
      var hits = domain.terms.filter(function (term) { return text.indexOf(term) >= 0; });
      return { domain: domain.name, confidence: hits.length / Math.max(1, domain.terms.length), signals: hits };
    }).sort(function (a, b) { return b.confidence - a.confidence; })[0] || { domain: "generic", confidence: 0, signals: [] };
    return scored.confidence >= 0.18 ? scored : { domain: "generic", confidence: scored.confidence, signals: scored.signals };
  }

  function analyticsSemanticProfile(metadata) {
    metadata = metadata || {};
    var domain = detectDatasetDomain(metadata);
    var columns = (metadata.headings || []).map(function (column) {
      var info = semanticColumnInfo(metadata, column);
      var key = info.key;
      var priority = 0;
      if (info.isNumeric) {
        if (/totalbill|totalbilling|hospitalbill|hospitalbilling|billingamount|charges/.test(key)) priority += 220;
        else if (/insuranceclaim|claimamount/.test(key)) priority += 125;
        else if (/patientpayment|outofpocket|oop/.test(key)) priority += 105;
        else if (/revenue|sales|amount|payment|cost|profit|salary|spend|premium|balance/.test(key)) priority += 90;
        if (/insurancecoverage/.test(key)) priority += 35;
        if (/treatmentcost|medicationcost|labcost|procedurecost/.test(key)) priority += 95;
        if (/quantity|qty|units|count|stock|volume/.test(key)) priority += 65;
        if (/satisfaction|score|rating|wait|stay|duration|days|minutes/.test(key)) priority += 48;
        if (info.percentage || info.role === "percentage") priority -= 100;
        if (info.demographic) priority -= 75;
      } else if (info.isCategory) {
        if (/department|diagnosis|procedure|admissiontype|bedtype|outcome|claimstatus|insuranceprovider|category|product|region|city|channel|status|manufacturer|supplier|vendor|segment/.test(key)) priority += 85;
        if (/gender|sex/.test(key)) priority -= 55;
        if (info.identifier || info.avoidDefault) priority -= 90;
      }
      if (info.identifier) priority -= 100;
      info.priority = priority;
      return info;
    });
    var metrics = columns.filter(function (item) { return item.isNumeric && !item.identifier && !item.avoidDefault && item.priority > 0; }).sort(function (a, b) { return b.priority - a.priority; });
    var dimensions = columns.filter(function (item) { return item.isCategory && !item.identifier && !item.avoidDefault && item.priority > 0; }).sort(function (a, b) { return b.priority - a.priority; });
    return {
      domain: domain.domain,
      confidence: domain.confidence,
      signals: domain.signals,
      columns: columns,
      metrics: metrics,
      dimensions: dimensions,
      primaryMetric: (metrics[0] || {}).name || "",
      primaryDimension: (dimensions[0] || {}).name || ""
    };
  }

  function safeDefaultMetricColumn(metadata) {
    var metrics = analyticsSemanticProfile(metadata).metrics || [];
    return (metrics.filter(function (item) { return item.role !== "percentage" && !item.percentage; })[0] || metrics[0] || {}).name || "";
  }

  function safeDefaultDimensionColumn(metadata) {
    return (analyticsSemanticProfile(metadata).dimensions[0] || {}).name || "";
  }

  function buildAdvancedAnalytics(metadata) {
    metadata = metadata || {};
    var rows = rowsForAnalytics(metadata);
    var headings = metadata.headings || [];
    var semanticProfile = analyticsSemanticProfile(metadata);
    var numericColumns = semanticProfile.metrics.map(function (item) { return item.name; });
    if (!numericColumns.length) numericColumns = Object.keys(metadata.numericStats || {}).filter(function (column) { return !semanticColumnInfo(metadata, column).avoidDefault; });
    var canonicalPrimaryMetric = canonicalPrimaryMetricColumn(metadata);
    if (canonicalPrimaryMetric && numericColumns.indexOf(canonicalPrimaryMetric) >= 0) {
      numericColumns = [canonicalPrimaryMetric].concat(numericColumns.filter(function (column) { return column !== canonicalPrimaryMetric; }));
    }
    var categoricalColumns = semanticProfile.dimensions.map(function (item) { return item.name; });
    if (!categoricalColumns.length) categoricalColumns = (metadata.categoricalColumns || headings.filter(function (heading) {
      var column = (metadata.columns || []).filter(function (item) { return item.name === heading; })[0] || {};
      return column.type === "text" || column.type === "boolean";
    })).filter(function (column) { return !semanticColumnInfo(metadata, column).avoidDefault; });
    categoricalColumns = categoricalColumns.slice(0, 12);
    var dateColumns = metadata.dateColumns || [];
    var aliases = columnAliases(headings);
    var numeric = {}, categoryFrequencies = {}, topBottom = {}, outliers = {}, dateTrends = {}, groupBy = {};
    var chartReady = { bar: [], line: [], pie: [], doughnut: [], scatter: [], kpi: [], trend: [], comparison: [], stackedBar: [] };

    numericColumns.slice(0, 24).forEach(function (column) {
      var values = rows.map(function (row) { return numericValue(rowValue(row, column)); }).filter(function (value) { return value !== null; }).sort(function (a, b) { return a - b; });
      var sum = values.reduce(function (a, b) { return a + b; }, 0);
      var q1 = percentile(values, 0.25);
      var q3 = percentile(values, 0.75);
      var iqr = q3 - q1;
      var lowFence = q1 - 1.5 * iqr;
      var highFence = q3 + 1.5 * iqr;
      numeric[column] = { count: values.length, min: roundNumber(values[0] || 0), max: roundNumber(values[values.length - 1] || 0), sum: roundNumber(sum), average: roundNumber(values.length ? sum / values.length : 0), median: roundNumber(percentile(values, 0.5)), q1: roundNumber(q1), q3: roundNumber(q3) };
      outliers[column] = { lowFence: roundNumber(lowFence), highFence: roundNumber(highFence), count: values.filter(function (value) { return value < lowFence || value > highFence; }).length, low: values.filter(function (value) { return value < lowFence; }).slice(0, 5).map(roundNumber), high: values.filter(function (value) { return value > highFence; }).slice(-5).map(roundNumber) };
      topBottom[column] = { top: values.slice(-10).reverse().map(roundNumber), bottom: values.slice(0, 10).map(roundNumber) };
    });

    categoricalColumns.forEach(function (column) {
      var freq = {};
      rows.forEach(function (row) {
        var value = String(rowValue(row, column) || "(Blank)").trim() || "(Blank)";
        freq[value] = (freq[value] || 0) + 1;
      });
      categoryFrequencies[column] = topEntries(freq, 10);
      chartReady.pie.push({ title: column + " share", labels: categoryFrequencies[column].map(function (item) { return item.name; }), values: categoryFrequencies[column].map(function (item) { return item.value; }) });
      chartReady.doughnut.push({ title: column + " distribution", labels: categoryFrequencies[column].map(function (item) { return item.name; }), values: categoryFrequencies[column].map(function (item) { return item.value; }) });
    });

    dateColumns.slice(0, 4).forEach(function (dateColumn) {
      var byMonth = {};
      rows.forEach(function (row) {
        var date = new Date(rowValue(row, dateColumn));
        if (Number.isNaN(date.getTime())) return;
        var month = date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, "0");
        if (!byMonth[month]) byMonth[month] = { count: 0 };
        byMonth[month].count += 1;
        numericColumns.slice(0, 6).forEach(function (numColumn) {
          byMonth[month][numColumn] = (byMonth[month][numColumn] || 0) + (numericValue(rowValue(row, numColumn)) || 0);
        });
      });
      dateTrends[dateColumn] = Object.keys(byMonth).sort().map(function (month) {
        var item = Object.assign({ month: month }, byMonth[month]);
        Object.keys(item).forEach(function (key) { if (key !== "month") item[key] = roundNumber(item[key]); });
        return item;
      }).slice(-24);
      chartReady.trend.push({ title: "Rows by month", dateColumn: dateColumn, labels: dateTrends[dateColumn].map(function (item) { return item.month; }), values: dateTrends[dateColumn].map(function (item) { return item.count; }) });
      if (numericColumns[0]) chartReady.line.push({ title: numericColumns[0] + " by month", dateColumn: dateColumn, labels: dateTrends[dateColumn].map(function (item) { return item.month; }), values: dateTrends[dateColumn].map(function (item) { return item[numericColumns[0]] || 0; }) });
    });

    categoricalColumns.slice(0, 8).forEach(function (catColumn) {
      numericColumns.slice(0, 5).forEach(function (numColumn) {
        var grouped = {};
        rows.forEach(function (row) {
          var key = String(rowValue(row, catColumn) || "(Blank)").trim() || "(Blank)";
          if (!grouped[key]) grouped[key] = { value: 0, count: 0 };
          grouped[key].value += numericValue(rowValue(row, numColumn)) || 0;
          grouped[key].count += 1;
        });
        var summary = topEntries(grouped, 10).map(function (item) {
          return { name: item.name, total: roundNumber(item.value), count: item.count, average: roundNumber(item.value / Math.max(1, item.count)) };
        });
        groupBy[catColumn + " by " + numColumn] = summary;
        chartReady.bar.push({ title: numColumn + " by " + catColumn, xAxis: catColumn, yAxis: numColumn, labels: summary.map(function (item) { return item.name; }), values: summary.map(function (item) { return item.total; }) });
      });
    });

    var correlations = [];
    for (var i = 0; i < numericColumns.length; i += 1) {
      for (var j = i + 1; j < numericColumns.length && correlations.length < 20; j += 1) {
        var xName = numericColumns[i], yName = numericColumns[j], pairs = [];
        rows.forEach(function (row) {
          var x = numericValue(rowValue(row, xName));
          var y = numericValue(rowValue(row, yName));
          if (x !== null && y !== null) pairs.push([x, y]);
        });
        if (pairs.length < 3) continue;
        var avgX = pairs.reduce(function (sum, pair) { return sum + pair[0]; }, 0) / pairs.length;
        var avgY = pairs.reduce(function (sum, pair) { return sum + pair[1]; }, 0) / pairs.length;
        var num = 0, denX = 0, denY = 0;
        pairs.forEach(function (pair) {
          var dx = pair[0] - avgX, dy = pair[1] - avgY;
          num += dx * dy;
          denX += dx * dx;
          denY += dy * dy;
        });
        var corr = denX && denY ? num / Math.sqrt(denX * denY) : 0;
        if (Math.abs(corr) >= 0.25) correlations.push({ columns: [xName, yName], correlation: roundNumber(corr), strength: Math.abs(corr) >= 0.7 ? "strong" : "moderate" });
      }
    }
    if (numericColumns.length >= 2) chartReady.scatter.push({ title: numericColumns[1] + " vs " + numericColumns[0], xAxis: numericColumns[0], yAxis: numericColumns[1] });

    function sumColumn(column) {
      return column ? rows.reduce(function (sum, row) { return sum + (numericValue(rowValue(row, column)) || 0); }, 0) : 0;
    }
    var revenueColumn = aliases.revenue;
    var profitColumn = aliases.profit;
    var quantityColumn = aliases.quantity;
    var discountColumn = aliases.discount;
    var orderColumn = aliases.orderId;
    var totalRevenue = sumColumn(revenueColumn);
    var totalProfit = sumColumn(profitColumn);
    var totalQuantity = sumColumn(quantityColumn);
    var orderCount = orderColumn ? Object.keys(rows.reduce(function (map, row) { map[String(rowValue(row, orderColumn) || "")] = true; return map; }, {})).length : rows.length;
    var ecommerce = { detectedColumns: aliases, totalRevenue: roundNumber(totalRevenue), totalProfit: roundNumber(totalProfit), totalQuantitySold: roundNumber(totalQuantity), averageOrderValue: roundNumber(orderCount ? totalRevenue / orderCount : 0), profitMargin: roundNumber(totalRevenue ? totalProfit / totalRevenue * 100 : 0) };
    ["category", "product", "city", "country", "paymentMethod"].forEach(function (key) {
      var column = aliases[key];
      if (!column) return;
      var map = {};
      rows.forEach(function (row) {
        var label = String(rowValue(row, column) || "(Blank)").trim() || "(Blank)";
        if (!map[label]) map[label] = { revenue: 0, profit: 0, quantity: 0, discount: 0, count: 0, value: 0 };
        map[label].revenue += revenueColumn ? numericValue(rowValue(row, revenueColumn)) || 0 : 0;
        map[label].profit += profitColumn ? numericValue(rowValue(row, profitColumn)) || 0 : 0;
        map[label].quantity += quantityColumn ? numericValue(rowValue(row, quantityColumn)) || 0 : 0;
        map[label].discount += discountColumn ? numericValue(rowValue(row, discountColumn)) || 0 : 0;
        map[label].count += 1;
        map[label].value = map[label].revenue || map[label].profit || map[label].quantity || map[label].count;
      });
      ecommerce[key + "Performance"] = topEntries(map, 10).map(function (item) { return { name: item.name, revenue: roundNumber(item.revenue), profit: roundNumber(item.profit), quantity: roundNumber(item.quantity), discount: roundNumber(item.discount), count: item.count }; });
      ecommerce["bottom" + key.charAt(0).toUpperCase() + key.slice(1)] = topEntries(map, 10, true).map(function (item) { return { name: item.name, revenue: roundNumber(item.revenue), profit: roundNumber(item.profit), quantity: roundNumber(item.quantity), discount: roundNumber(item.discount), count: item.count }; });
    });
    if (aliases.date && (revenueColumn || profitColumn)) {
      ecommerce.revenueByMonth = dateTrends[aliases.date] || [];
      ecommerce.profitByMonth = ecommerce.revenueByMonth;
    }
    if (discountColumn && profitColumn) {
      ecommerce.discountVsProfitImpact = correlations.filter(function (item) { return item.columns.indexOf(discountColumn) >= 0 && item.columns.indexOf(profitColumn) >= 0; })[0] || { columns: [discountColumn, profitColumn], correlation: 0, strength: "insufficient" };
    }

    chartReady.kpi = numericColumns.slice(0, 6).map(function (name) {
      var stats = metadata.numericStats[name] || {};
      var role = semanticColumnInfo(metadata, name).role;
      var averageMode = role === "measure_avg" || role === "percentage";
      return { title: (averageMode ? "Average " : "Total ") + columnLabel(name), value: chartNumber(averageMode ? stats.average || 0 : stats.sum || 0), formula: (averageMode ? "AVG" : "SUM") + " `" + name + "`" };
    });
    chartReady.comparison = chartReady.bar.slice(0, 4);
    chartReady.stackedBar = chartReady.bar.slice(0, 4);
    var missingTotal = Object.keys(metadata.missingValues || {}).reduce(function (sum, key) { return sum + (Number(metadata.missingValues[key]) || 0); }, 0);
    var totalCells = Math.max(1, (metadata.rowCount || rows.length || 0) * (metadata.columnCount || headings.length || 1));
    var qualityScore = Math.max(0, Math.min(100, 100 - (missingTotal / totalCells * 45) - ((metadata.duplicateRows || 0) / Math.max(1, metadata.rowCount || rows.length || 1) * 35) - ((metadata.emptyRows || 0) / Math.max(1, metadata.rowCount || rows.length || 1) * 20)));
    return {
      coverage: { rowsAnalyzed: rows.length || metadata.rowCount || 0, columnsAnalyzed: headings.length, rawRowsSentToAI: false, analyticsEngine: "browser-full-dataset" },
      shape: { rowCount: metadata.rowCount || rows.length || 0, columnCount: metadata.columnCount || headings.length || 0, emptyRows: metadata.emptyRows || 0, duplicateRows: metadata.duplicateRows || 0 },
      missingValues: metadata.missingValues || {},
      dataTypes: (metadata.columns || []).map(function (column) { return { name: column.name, type: column.type, missing: column.missing, unique: column.unique }; }),
      numeric: numeric,
      categoryFrequencies: categoryFrequencies,
      topBottomValues: topBottom,
      dateTrends: dateTrends,
      groupBySummaries: groupBy,
      outliers: outliers,
      correlationHints: correlations,
      semanticProfile: {
        domain: semanticProfile.domain,
        confidence: semanticProfile.confidence,
        signals: semanticProfile.signals,
        primaryMetric: canonicalPrimaryMetric || semanticProfile.primaryMetric,
        primaryDimension: semanticProfile.primaryDimension,
        metrics: semanticProfile.metrics.slice(0, 12).map(function (item) { return { name: item.name, role: item.role, priority: item.priority }; }),
        dimensions: semanticProfile.dimensions.slice(0, 12).map(function (item) { return { name: item.name, role: item.role, priority: item.priority }; })
      },
      ecommerce: /^(ecommerce|sales|pharma)$/.test(semanticProfile.domain) ? ecommerce : {},
      chartReady: chartReady,
      dataQualityScore: roundNumber(qualityScore),
      executiveSummaryInputs: { strongestMetrics: topEntries(numeric, 8), largestCategories: Object.keys(categoryFrequencies).slice(0, 6).reduce(function (out, key) { out[key] = categoryFrequencies[key].slice(0, 5); return out; }, {}), notableOutliers: Object.keys(outliers).filter(function (key) { return outliers[key].count > 0; }).slice(0, 8) },
      recommendedActions: [
        missingTotal ? "Prioritize columns with the highest missing values before using the sheet for decisions." : "Missing values are limited based on the parsed dataset.",
        (metadata.duplicateRows || 0) ? "Review duplicate rows before reporting final KPIs." : "Duplicate row risk appears low from the parsed dataset.",
        correlations.length ? "Use the strongest correlation hints for deeper business investigation, not as proof of causation." : "Correlation evidence is limited or weak across numeric columns.",
        semanticProfile.primaryDimension && (canonicalPrimaryMetric || semanticProfile.primaryMetric) ? "Use " + columnLabel(semanticProfile.primaryDimension) + " by " + columnLabel(canonicalPrimaryMetric || semanticProfile.primaryMetric) + " summaries to prioritize deeper review." : "Use high-confidence metrics and dimensions for deeper review."
      ]
    };
  }

  function metadataFromRows(rows, file) {
    var headings = (rows[0] || []).map(function (cell, index) { return String(cell || "").trim() || "Column " + (index + 1); });
    var dataRows = rows.slice(1);
    var emptyRows = dataRows.filter(function (row) { return !row.some(function (cell) { return String(cell || "").trim() !== ""; }); }).length;
    var seen = {}, duplicateRows = 0;
    dataRows.forEach(function (row) {
      var key = JSON.stringify(row.map(function (cell) { return String(cell || "").trim(); }));
      if (seen[key]) duplicateRows += 1;
      seen[key] = true;
    });
    var columns = headings.map(function (heading, index) {
      var values = dataRows.map(function (row) { return row[index] == null ? "" : String(row[index]); });
      var type = inferType(values);
      var nums = values.map(numericValue).filter(function (n) { return n !== null; });
      var uniqueMap = {};
      values.forEach(function (value) { if (String(value).trim() !== "") uniqueMap[String(value).trim()] = true; });
      return {
        name: heading,
        type: type,
        missing: values.filter(function (value) { return String(value).trim() === ""; }).length,
        unique: Object.keys(uniqueMap).length,
        stats: nums.length ? {
          min: Math.min.apply(null, nums),
          max: Math.max.apply(null, nums),
          average: nums.reduce(function (a, b) { return a + b; }, 0) / nums.length,
          sum: nums.reduce(function (a, b) { return a + b; }, 0)
        } : null,
        examples: Object.keys(uniqueMap).slice(0, 5)
      };
    });
    var numericStats = {}, missingValues = {};
    columns.forEach(function (column) {
      missingValues[column.name] = column.missing;
      if (column.stats) numericStats[column.name] = column.stats;
    });
    var metadata = {
      fileName: file.name,
      fileType: (file.name.split(".").pop() || "").toLowerCase(),
      fileSize: file.size,
      rowCount: dataRows.length,
      columnCount: headings.length,
      emptyRows: emptyRows,
      duplicateRows: duplicateRows,
      headings: headings,
      columns: columns,
      numericStats: numericStats,
      missingValues: missingValues,
      dateColumns: columns.filter(function (column) { return column.type === "date"; }).map(function (column) { return column.name; }),
      categoricalColumns: columns.filter(function (column) { return column.type === "text" && column.unique <= Math.max(20, dataRows.length * 0.25); }).map(function (column) { return column.name; })
    };
    Object.defineProperty(metadata, "_localRows", {
      value: dataRows.map(function (row) {
        var out = {};
        headings.forEach(function (heading, index) { out[heading] = row[index] == null ? "" : String(row[index]); });
        return out;
      }),
      enumerable: false
    });
    return metadata;
  }

  function parseFile(file, progress) {
    var ext = (file.name.split(".").pop() || "").toLowerCase();
    if (!/^(csv|xlsx)$/.test(ext)) return Promise.reject(new Error("Upload a CSV or XLSX file."));
    if (file.size <= 0 || file.size > MAX_FILE_SIZE) return Promise.reject(new Error("Sheet must be 10MB or smaller."));
    progress(20, "Reading sheet");
    if (ext === "csv") {
      return file.text().then(function (text) {
        progress(45, "Detecting columns");
        var rows = parseCsv(text);
        progress(70, "Preparing metadata");
        return metadataFromRows(rows, file);
      });
    }
    return parseXlsx(file).then(function (rows) {
      progress(70, "Preparing metadata");
      return metadataFromRows(rows, file);
    });
  }

  function list(items) {
    return "<ul>" + (items || []).slice(0, 6).map(function (item) {
      return "<li>" + esc(plainItem(item)) + "</li>";
    }).join("") + "</ul>";
  }

  function plainItem(item) {
    if (typeof item === "string") return item;
    if (typeof item === "number" || typeof item === "boolean") return String(item);
    if (!item) return "";
    if (Array.isArray(item)) return item.map(plainItem).filter(Boolean).join("; ");
    var keys = Object.keys(item);
    var narrativeKeys = ["text", "title", "issue", "insight", "suggestion", "reason", "notes"];
    var nonNarrativeKeys = keys.filter(function (key) { return narrativeKeys.indexOf(key) < 0; });
    if (!nonNarrativeKeys.length && (item.text || item.title || item.issue || item.insight || item.suggestion || item.reason || item.notes)) {
      return item.text || item.title || item.issue || item.insight || item.suggestion || item.reason || item.notes;
    }
    return keys.map(function (key) {
      var label = key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2");
      label = label.charAt(0).toUpperCase() + label.slice(1);
      return label + ": " + plainItem(item[key]);
    }).filter(Boolean).join("; ");
  }

  function listItems(items, fallback) {
    var out = (items || []).map(plainItem).filter(Boolean).map(function (text) {
      return { text: text, level: 0 };
    });
    return out.length ? out : [{ text: fallback || "", level: 0 }];
  }

  function pushAnalysisSection(blocks, title, items, limit, fallback) {
    items = (items || []).slice(0, limit || 6);
    if (!items.length && !fallback) return;
    blocks.push({ type: "heading", data: { level: 3, text: title } });
    blocks.push({ type: "list", data: { style: "unordered", items: listItems(items, fallback) } });
  }

  function sentenceList(items, fallback) {
    var text = (items || []).map(plainItem).filter(Boolean).join(" ");
    return text || fallback || "";
  }

  function chartTitle(chart, index) {
    return chart && (chart.title || chart.name) || ("Recommended Chart " + (index + 1));
  }

  function normalizeRecommendedChartType(type) {
    type = String(type || "bar").replace(/\s+/g, "").replace(/-/g, "").toLowerCase();
    if (type === "stackedbarchart" || type === "stackedbar") return "stackedBar";
    if (type === "kpicards" || type === "kpi") return "kpi";
    if (type === "barchart") return "bar";
    if (type === "linechart") return "line";
    if (type === "piechart") return "pie";
    if (type === "doughnutchart" || type === "donutchart") return "doughnut";
    if (type === "areachart") return "area";
    if (type === "scatterchart") return "scatter";
    if (type === "comparisonchart") return "comparison";
    if (type === "trendchart") return "trend";
    return CHART_TYPES.indexOf(type) >= 0 ? type : "bar";
  }

  function rowValue(row, column) {
    if (!row || !column) return "";
    return row[column] == null ? "" : row[column];
  }

  function chartNumber(value) {
    var number = Number(value) || 0;
    return Math.round(number * 100) / 100;
  }

  function formatPercent(value) {
    var number = Number(value) || 0;
    return chartNumber(number) + "%";
  }

  function columnKey(value) {
    return String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, "");
  }

  function escapeRegExp(value) {
    return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  }

  function resolveColumn(metadata, requested, kind) {
    var headings = metadata.headings || [];
    var numeric = Object.keys(metadata.numericStats || {});
    var categorical = metadata.categoricalColumns || [];
    var fallback = kind === "number" ? (safeDefaultMetricColumn(metadata) || numeric.filter(function (column) { return !semanticColumnInfo(metadata, column).avoidDefault; })[0] || numeric[0] || headings[1] || headings[0]) : (safeDefaultDimensionColumn(metadata) || categorical.filter(function (column) { return !semanticColumnInfo(metadata, column).avoidDefault; })[0] || headings[0]);
    if (!requested) return fallback;
    var wanted = columnKey(requested);
    var exact = headings.filter(function (heading) { return columnKey(heading) === wanted; })[0];
    if (exact) return exact;
    var partial = headings.filter(function (heading) {
      var key = columnKey(heading);
      return key.indexOf(wanted) >= 0 || wanted.indexOf(key) >= 0;
    }).sort(function (a, b) { return columnKey(b).length - columnKey(a).length; })[0];
    return partial || fallback;
  }

  function chartText(chart) {
    return String([
      chart && chart.title,
      chart && chart.name,
      chart && chart.reason,
      chart && chart.description,
      chart && chart.xAxis,
      chart && chart.x_axis,
      chart && chart.yAxis,
      chart && chart.y_axis,
      chart && chart.metric,
      chart && chart.aggregation,
      chart && chart.groupBy,
      chart && chart.group_by
    ].filter(Boolean).join(" ")).toLowerCase();
  }

  function chartTitleText(chart) {
    return String([chart && chart.title, chart && chart.name].filter(Boolean).join(" ")).toLowerCase();
  }

  function resolveChartColumn(metadata, text, chart, field, fallbackKind) {
    metadata = metadata || {};
    chart = chart || {};
    var headings = metadata.headings || [];
    var aliases = columnAliases(headings);
    var explicit = chart[field] || chart[field.replace(/[A-Z]/g, function (letter) { return "_" + letter.toLowerCase(); })];
    if (explicit) return resolveColumn(metadata, explicit, fallbackKind);
    var candidates = [
      { words: ["payment method", "payment", "method"], column: aliases.paymentMethod },
      { words: ["category", "categories"], column: aliases.category },
      { words: ["product", "sku", "item"], column: aliases.product },
      { words: ["city"], column: aliases.city },
      { words: ["country"], column: aliases.country },
      { words: ["month", "monthly", "date", "trend", "time"], column: aliases.date },
      { words: ["revenue", "sales", "amount"], column: aliases.revenue },
      { words: ["profit", "margin"], column: aliases.profit },
      { words: ["discount"], column: aliases.discount },
      { words: ["quantity", "qty", "units"], column: aliases.quantity },
      { words: ["order id", "orderid", "orders"], column: aliases.orderId }
    ];
    for (var i = 0; i < candidates.length; i += 1) {
      if (!candidates[i].column) continue;
      if (candidates[i].words.some(function (word) { return text.indexOf(word) >= 0; })) return candidates[i].column;
    }
    return fallbackKind === "number" ? safeDefaultMetricColumn(metadata) : (safeDefaultDimensionColumn(metadata) || resolveColumn(metadata, "", fallbackKind));
  }

  function metricColumnFromPhrase(metadata, phrase) {
    var aliases = columnAliases((metadata && metadata.headings) || []);
    phrase = String(phrase || "").toLowerCase();
    if (/\brevenue|sales|amount\b/.test(phrase) && aliases.revenue) return aliases.revenue;
    if (/\bprofit|margin\b/.test(phrase) && aliases.profit) return aliases.profit;
    if (/\bdiscount\b/.test(phrase) && aliases.discount) return aliases.discount;
    if (/\bquantity|qty|units\b/.test(phrase) && aliases.quantity) return aliases.quantity;
    return "";
  }

  function resolveChartMetricColumn(metadata, text, chart, field) {
    chart = chart || {};
    var title = chartTitleText(chart);
    var titleMetric = metricColumnFromPhrase(metadata, title);
    var explicit = chart[field] || chart[field.replace(/[A-Z]/g, function (letter) { return "_" + letter.toLowerCase(); })];
    if (explicit) return resolveColumn(metadata, explicit, "number");
    if (titleMetric) return titleMetric;
    if (chart.metric && !/\b(count|frequency|distribution|mix|share|number of|how many)\b/i.test(String(chart.metric))) return resolveColumn(metadata, chart.metric, "number");
    var vs = text.match(/([^.;:,]+?)\s+v(?:s|ersus)\.?\s+([^.;:,]+)/);
    if (vs) {
      var phrase = field === "xAxis" ? vs[1] : vs[2];
      var metric = metricColumnFromPhrase(metadata, phrase);
      if (metric) return metric;
    }
    var aliases = columnAliases((metadata && metadata.headings) || []);
    var candidates = [
      { key: "revenue", column: aliases.revenue },
      { key: "profit", column: aliases.profit },
      { key: "discount", column: aliases.discount },
      { key: "quantity", column: aliases.quantity }
    ].filter(function (item) { return item.column && text.indexOf(item.key) >= 0; });
    if (candidates.length) {
      candidates.sort(function (a, b) { return text.indexOf(a.key) - text.indexOf(b.key); });
      return candidates[0].column;
    }
    return safeDefaultMetricColumn(metadata) || resolveColumn(metadata, "", "number");
  }

  function chartAggregation(chart, text) {
    var explicit = String((chart && chart.aggregation) || "").toLowerCase();
    var title = chartTitleText(chart);
    var hasMetric = /\brevenue|sales|amount|profit|margin|discount|quantity|qty|units\b/.test(title);
    var hasPercentageMetric = /\bpct\b|percent|percentage|\brate\b|ratio|share|%/.test(title);
    var hasCountIntent = /\b(count|frequency|number of|how many|order count|orders count)\b/.test(title) || (/\b(mix|distribution)\b/.test(title) && !hasMetric);
    if (/\b(count|frequency|distribution|mix|share|number of|how many)\b/.test(explicit)) return "count";
    if (/\b(avg|average|mean|per order|per row)\b/.test(explicit)) return "average";
    if (/\b(min|minimum)\b/.test(explicit)) return "min";
    if (/\b(max|maximum)\b/.test(explicit)) return "max";
    if (hasCountIntent) return "count";
    if (/\b(avg|average|mean|per order|per row)\b/.test(title)) return "average";
    if (hasPercentageMetric) return "average";
    return "sum";
  }

  function chartValueForBucket(bucket, aggregation) {
    bucket = bucket || { total: 0, count: 0, min: 0, max: 0 };
    if (aggregation === "count") return bucket.count || 0;
    if (aggregation === "average" || aggregation === "avg") return bucket.total / Math.max(1, bucket.count || 0);
    if (aggregation === "min") return bucket.count ? bucket.min : 0;
    if (aggregation === "max") return bucket.count ? bucket.max : 0;
    return bucket.total || 0;
  }

  function chartTopLimit(chart, text, type) {
    var match = text.match(/\btop\s+(\d{1,2})\b/);
    if (match) return Math.max(1, Math.min(30, Number(match[1]) || 10));
    if (type === "kpi") return 12;
    if (/\bmonth|monthly|trend|time series\b/.test(text)) return 24;
    if (/\bproduct|sku|item\b/.test(text)) return 10;
    return 12;
  }

  function monthKeyAndLabel(value) {
    var date = new Date(value);
    if (Number.isNaN(date.getTime())) return null;
    var key = date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, "0");
    var label = date.toLocaleString("en-US", { month: "short", year: "numeric" });
    return { key: key, label: label };
  }

  function chartCategoryLabel(value) {
    var text = String(value == null ? "" : value).trim();
    if (!text) return "Blank";
    if (/^row\s+\d+$/i.test(text)) return "Blank";
    return text;
  }

  function evenlySampleRows(rows, limit) {
    rows = rows || [];
    if (rows.length <= limit) return rows.slice();
    var out = [];
    for (var i = 0; i < limit; i += 1) out.push(rows[Math.floor(i * rows.length / limit)]);
    return out;
  }

  function chartMetricColumnsForText(metadata, text, chart) {
    metadata = metadata || {};
    chart = chart || {};
    text = String(text || "");
    var aliases = columnAliases(metadata.headings || []);
    var candidates = [
      { words: ["revenue", "sales", "amount"], column: aliases.revenue },
      { words: ["profit", "margin"], column: aliases.profit },
      { words: ["discount"], column: aliases.discount },
      { words: ["quantity", "qty", "units"], column: aliases.quantity }
    ];
    var metrics = [];
    candidates.forEach(function (item) {
      if (!item.column) return;
      if (item.words.some(function (word) { return new RegExp("\\b" + word + "\\b", "i").test(text); }) && metrics.indexOf(item.column) < 0) {
        metrics.push(item.column);
      }
    });
    ["yAxis", "metric", "value", "values"].forEach(function (field) {
      var raw = chart[field] || chart[field.replace(/[A-Z]/g, function (letter) { return "_" + letter.toLowerCase(); })];
      if (!raw) return;
      String(raw).split(/[,;/]+/).forEach(function (part) {
        var metric = resolveColumn(metadata, part, "number");
        if (metric && Object.prototype.hasOwnProperty.call(metadata.numericStats || {}, metric) && metrics.indexOf(metric) < 0) metrics.push(metric);
      });
    });
    return metrics.filter(function (metric, index, list) { return metric && list.indexOf(metric) === index; });
  }

  function buildChartData(metadata, chart, index) {
    var rows = rowsForAnalytics(metadata);
    var headings = metadata.headings || [];
    var numeric = Object.keys(metadata.numericStats || {});
    var text = chartText(chart);
    var xAxis = resolveChartColumn(metadata, text, chart, "xAxis", "category");
    var yAxis = resolveChartMetricColumn(metadata, text, chart, "yAxis");
    var requestedGroup = chart.groupBy || chart.group_by;
    var groupBy = requestedGroup ? resolveColumn(metadata, requestedGroup, "category") : "";
    var type = normalizeRecommendedChartType(chart.chartType || chart.type);
    var aggregation = chartAggregation(chart, text);
    var dateColumn = (metadata.dateColumns || [])[0] || resolveChartColumn(metadata, text, chart, "xAxis", "category");
    var groupByMonth = /\b(month|monthly|monthwise|month-wise|trend|time series)\b/.test(text) && dateColumn;
    var aliases = columnAliases(headings);
    var metricSeriesColumns = chartMetricColumnsForText(metadata, text, chart);
    if (chart && chart.labels != null && chart.values != null) {
      var explicitLabels = chartListValue(chart.labels);
      var explicitValues = chartListValue(chart.values);
      return {
        type: type,
        title: chartTitle(chart, index),
        labels: explicitLabels,
        values: explicitValues,
        caption: (chart.reason || "Generated from exact parsed sheet values.") + " Generated from " + rows.length + " parsed workbook rows.",
        color: "#2563eb",
        colors: ["#2563eb", "#16a34a", "#f59e0b", "#dc2626", "#7c3aed", "#0891b2", "#db2777", "#65a30d", "#ea580c", "#475569"].join(",")
      };
    }
    if (groupByMonth) xAxis = dateColumn;
    if (aggregation === "count") yAxis = "";
    if ((type === "comparison" || type === "stackedBar") && !groupBy && metricSeriesColumns.length < 2 && !(groupByMonth && /\brevenue\b/.test(text) && /\bprofit\b/.test(text))) type = "bar";

    if (type === "scatter") {
      var scatterRows = evenlySampleRows(rows, 80);
      var xNumeric = resolveChartMetricColumn(metadata, text, chart, "xAxis");
      var yNumeric = yAxis || resolveChartMetricColumn(metadata, text, chart, "yAxis");
      var pairs = scatterRows.map(function (row) {
        var xValue = numericValue(rowValue(row, xNumeric));
        var yValue = numericValue(rowValue(row, yNumeric));
        return xValue === null || yValue === null ? null : { x: xValue, y: yValue };
      }).filter(Boolean);
      return {
        type: type,
        title: chartTitle(chart, index),
        labels: pairs.map(function (pair) { return chartNumber(pair.x); }).join(","),
        values: pairs.map(function (pair) { return chartNumber(pair.y); }).join(","),
        xAxisLabel: xNumeric,
        yAxisLabel: yNumeric,
        caption: (chart.reason || "Scatter chart generated from parsed sheet rows.") + " Each dot is one displayed row/order. Horizontal axis X = " + xNumeric + "; vertical axis Y = " + yNumeric + ". Points shown: " + pairs.length + " evenly selected display points from " + rows.length + " parsed workbook rows.",
        color: "#2563eb",
        colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
      };
    }

    if (groupByMonth && aliases.revenue && aliases.profit && /\brevenue\b/.test(text) && /\bprofit\b/.test(text)) {
      var monthly = {};
      rows.forEach(function (row) {
        var month = monthKeyAndLabel(rowValue(row, xAxis));
        if (!month) return;
        if (!monthly[month.label]) monthly[month.label] = { sortKey: month.key, revenue: 0, profit: 0 };
        monthly[month.label].revenue += numericValue(rowValue(row, aliases.revenue)) || 0;
        monthly[month.label].profit += numericValue(rowValue(row, aliases.profit)) || 0;
      });
      var monthLabels = Object.keys(monthly).sort(function (a, b) { return String(monthly[a].sortKey).localeCompare(String(monthly[b].sortKey)); }).slice(-chartTopLimit(chart, text, type));
      return {
        type: "comparison",
        title: chartTitle(chart, index),
        labels: monthLabels.join(","),
        values: monthLabels.map(function (label) { return chartNumber(monthly[label].revenue); }).join(",") + "; " + monthLabels.map(function (label) { return chartNumber(monthly[label].profit); }).join(","),
        seriesLabels: "Revenue,Profit",
        caption: (chart.reason || "Monthly revenue and profit comparison generated from parsed sheet rows.") + " Generated from " + rows.length + " parsed workbook rows. Aggregation: sum by month. Series 1 = " + aliases.revenue + "; Series 2 = " + aliases.profit + ".",
        color: "#2563eb",
        colors: "#2563eb,#16a34a"
      };
    }

    if ((type === "comparison" || type === "stackedBar" || /\bvs|versus|compare|comparison\b/i.test(text)) && metricSeriesColumns.length >= 2 && xAxis) {
      var metricGrouped = {};
      var metricLabelLookup = {};
      rows.forEach(function (row, rowIndex) {
        var month = groupByMonth ? monthKeyAndLabel(rowValue(row, xAxis)) : null;
        var key = month ? month.key : chartCategoryLabel(rowValue(row, xAxis));
        var label = month ? month.label : key;
        if (!metricGrouped[label]) {
          metricGrouped[label] = { sortKey: month ? key : label, metrics: {} };
          metricLabelLookup[label] = true;
        }
        metricSeriesColumns.forEach(function (metric) {
          metricGrouped[label].metrics[metric] = (metricGrouped[label].metrics[metric] || 0) + (numericValue(rowValue(row, metric)) || 0);
        });
      });
      var metricLabels = Object.keys(metricLabelLookup);
      if (groupByMonth) {
        metricLabels.sort(function (a, b) { return String(metricGrouped[a].sortKey).localeCompare(String(metricGrouped[b].sortKey)); });
      } else {
        metricLabels.sort(function (a, b) {
          return (metricGrouped[b].metrics[metricSeriesColumns[0]] || 0) - (metricGrouped[a].metrics[metricSeriesColumns[0]] || 0) || String(a).localeCompare(String(b));
        });
      }
      metricLabels = metricLabels.slice(0, chartTopLimit(chart, text, type));
      var metricValues = metricSeriesColumns.map(function (metric) {
        return metricLabels.map(function (label) { return chartNumber((metricGrouped[label].metrics || {})[metric] || 0); }).join(",");
      }).join(";");
      var metricPalette = ["#2563eb", "#16a34a", "#f59e0b", "#dc2626", "#7c3aed", "#0891b2"];
      return {
        type: type === "stackedBar" ? "stackedBar" : "comparison",
        title: chartTitle(chart, index),
        labels: metricLabels.join(","),
        values: metricValues,
        seriesLabels: metricSeriesColumns.join(","),
        caption: (chart.reason || "Multi-metric comparison generated from parsed sheet rows.") + " Generated from " + rows.length + " parsed workbook rows. Formula: group by `" + xAxis + "`, then sum each metric series: " + metricSeriesColumns.join(", ") + ".",
        color: metricPalette[0],
        colors: metricPalette.slice(0, Math.max(1, metricSeriesColumns.length)).join(",")
      };
    }

    var grouped = {};
    var labelLookup = {};
    rows.forEach(function (row, rowIndex) {
      var month = groupByMonth ? monthKeyAndLabel(rowValue(row, xAxis)) : null;
      var key = month ? month.key : chartCategoryLabel(rowValue(row, xAxis));
      var label = month ? month.label : key;
      var rawValue = aggregation === "count" ? 1 : numericValue(rowValue(row, yAxis));
      var value = rawValue === null ? 0 : rawValue;
      if (!grouped[label]) {
        grouped[label] = { total: 0, count: 0, min: value, max: value, series: {}, sortKey: month ? key : label };
        labelLookup[label] = true;
      }
      grouped[label].total += value;
      grouped[label].count += 1;
      grouped[label].min = grouped[label].count === 1 ? value : Math.min(grouped[label].min, value);
      grouped[label].max = grouped[label].count === 1 ? value : Math.max(grouped[label].max, value);
      if (groupBy) {
        var seriesName = chartCategoryLabel(rowValue(row, groupBy));
        grouped[label].series[seriesName] = (grouped[label].series[seriesName] || 0) + value;
      }
    });
    var labels = Object.keys(labelLookup);
    var maxLabels = chartTopLimit(chart, text, type);
    if (groupByMonth) {
      labels.sort(function (a, b) { return String(grouped[a].sortKey).localeCompare(String(grouped[b].sortKey)); });
    } else if (/\b(top|highest|largest|revenue|profit|discount|quantity|count|mix|distribution|share)\b/.test(text) || aggregation !== "sum") {
      labels.sort(function (a, b) {
        return chartValueForBucket(grouped[b], aggregation) - chartValueForBucket(grouped[a], aggregation) || String(a).localeCompare(String(b));
      });
    }
    if (!groupByMonth && labels.length > maxLabels) {
      var keep = labels.slice(0, maxLabels - 1);
      var other = labels.slice(maxLabels - 1);
      grouped.Other = other.reduce(function (bucket, label) {
        var item = grouped[label] || { total: 0, count: 0, min: 0, max: 0, series: {} };
        bucket.total += item.total;
        bucket.count += item.count;
        bucket.min = bucket.count === item.count ? item.min : Math.min(bucket.min, item.min);
        bucket.max = bucket.count === item.count ? item.max : Math.max(bucket.max, item.max);
        Object.keys(item.series || {}).forEach(function (seriesName) {
          bucket.series[seriesName] = (bucket.series[seriesName] || 0) + item.series[seriesName];
        });
        return bucket;
      }, { total: 0, count: 0, min: 0, max: 0, series: {} });
      labels = keep.concat(["Other"]);
    } else {
      labels = labels.slice(0, maxLabels);
    }
    var values = labels.map(function (label) {
      return chartNumber(chartValueForBucket(grouped[label], aggregation));
    });
    if ((type === "stackedBar" || type === "comparison") && groupBy) {
      var seriesNames = [];
      labels.forEach(function (label) {
        Object.keys((grouped[label] || {}).series || {}).forEach(function (name) {
          if (seriesNames.indexOf(name) < 0) seriesNames.push(name);
        });
      });
      values = seriesNames.slice(0, 4).map(function (seriesName) {
        return labels.map(function (label) { return chartNumber(((grouped[label] || {}).series || {})[seriesName] || 0); }).join(",");
      }).join("; ");
    } else {
      values = values.join(",");
    }
    if (!labels.length || !String(values).replace(/[0,.;\s-]/g, "")) {
      labels = numeric.slice(0, type === "kpi" ? 12 : 8);
      values = labels.map(function (name) {
        var stats = metadata.numericStats[name] || {};
        return chartNumber(String(chart.aggregation || "").toLowerCase().indexOf("avg") >= 0 ? stats.average || 0 : stats.sum || stats.average || stats.max || 0);
      }).join(",");
      if (!labels.length) {
        labels = ["Rows", "Columns", "Duplicates"];
        values = [metadata.rowCount || 0, metadata.columnCount || 0, metadata.duplicateRows || 0].join(",");
      }
    }
    var palette = ["#2563eb", "#16a34a", "#f59e0b", "#dc2626", "#7c3aed", "#0891b2", "#db2777", "#65a30d", "#ea580c", "#475569"];
    return {
      type: type,
      title: chartTitle(chart, index),
      labels: labels.join(","),
      values: values,
      caption: (chart.reason || ("Suggested using " + (xAxis || "the detected category column") + " and " + (yAxis || "row count") + ".")) + " Generated from " + rows.length + " parsed workbook rows. Aggregation: " + aggregation + (groupByMonth ? " by month" : "") + ".",
      color: "#2563eb",
      colors: palette.slice(0, Math.max(1, labels.length)).join(",")
    };
  }

  function analyticsChartIntentText(question) {
    return String(question || "")
      .replace(/\bdo\s+not\b[^.;\n]*(?:chart|graph|plot|visual)[^.;\n]*/ig, " ")
      .replace(/\bno\s+(?:chart|graph|plot|visual)[^.;\n]*/ig, " ")
      .replace(/\b(?:chart|graph|plot|visual)\s+only\s+if\s+it\s+fits\b/ig, " ")
      .replace(/\breadable\s+comparison\s+chart\s+only\s+if\s+it\s+fits\b/ig, " ")
      .replace(/\bvalidate\s+(?:the\s+)?table\s+and\s+chart\s+consistency[^.;\n]*/ig, " ")
      .replace(/\bstate\s+the\s+exact\s+grouping\s+and\s+formula[^.;\n]*/ig, " ");
  }

  function splitAnalyticsDimensionName(dimension) {
    return String(dimension || "")
      .split(/\s*(?:\+|&|\/|\band\b|\bplus\b)\s*/i)
      .map(function (part) { return part.trim(); })
      .filter(Boolean);
  }

  function analyticsHeaderOrTextHasDimension(headers, allText, dimension) {
    var parts = splitAnalyticsDimensionName(dimension);
    if (!parts.length) parts = [String(dimension || "").trim()].filter(Boolean);
    return parts.every(function (part) {
      var normalized = String(part || "").toLowerCase();
      return !normalized || headers.indexOf(normalized) >= 0 || String(allText || "").toLowerCase().indexOf(normalized) >= 0;
    });
  }

  function pivotColumnDimensionIsRepresented(metadata, pivot, blocks) {
    if (!metadata || !pivot || !pivot.columnColumn) return false;
    var tables = nestedTables(blocks || []);
    if (!tables.length) return false;
    var rows = tables[0] && tables[0].data && Array.isArray(tables[0].data.rows) ? tables[0].data.rows : [];
    var headers = (rows[0] || []).map(function (heading) { return String(heading || "").trim().toLowerCase(); });
    var values = {};
    rowsForAnalytics(metadata).forEach(function (row) {
      var value = String(rowValue(row, pivot.columnColumn) || "Blank").trim() || "Blank";
      values[value.toLowerCase()] = true;
    });
    return Object.keys(values).some(function (value) { return headers.indexOf(value) >= 0; });
  }

  function asksForGraph(question) {
    if (isPivotTableOnlyPrompt(question)) return false;
    return /\b(graph|chart|plot|visual|visualize|show\s+it|show\s+in\s+a\s+graph)\b/i.test(analyticsChartIntentText(question));
  }

  function asksForTrend(question) {
    return /\b(trend|line\s+chart|over\s+time|time\s+series)\b/i.test(String(question || ""));
  }

  function requestedChartType(question) {
    if (isPivotTableOnlyPrompt(question)) return "";
    var q = analyticsChartIntentText(question);
    if (/\bscatter(?:\s+chart)?\b/i.test(q)) return "scatter";
    if (/\bstacked(?:\s+bar)?(?:\s+chart)?\b/i.test(q)) return "stackedBar";
    if (/\bcomparison(?:\s+chart)?\b/i.test(q)) return "comparison";
    if (/\bdoughnut|donut\b/i.test(q)) return "doughnut";
    if (/\bpie(?:\s+chart)?\b/i.test(q)) return "pie";
    if (/\bkpi(?:\s+card|s)?\b/i.test(q)) return "kpi";
    if (/\bline(?:\s+chart)?\b|\btrend(?:\s+chart)?\b/i.test(q)) return "line";
    if (/\bbar\s+(?:chart|graph|plot)\b|\b(?:chart|graph|plot)\s+(?:as\s+)?bar\b|\bcolumn\s+(?:chart|graph|plot)\b|\b(?:chart|graph|plot)\s+(?:as\s+)?column\b/i.test(q)) return "bar";
    return "";
  }

  function isPivotTableOnlyPrompt(question) {
    var q = analyticsChartIntentText(question);
    if (!/\b(matrix|pivot)\s+table\b/i.test(q)) return false;
    return !/\b(stacked\s+bar|bar\s+chart|line\s+chart|pie\s+chart|donut\s+chart|doughnut\s+chart|scatter\s+chart|comparison\s+chart|chart|graph|plot|visualize)\b/i.test(q.replace(/\b(matrix|pivot)\s+table\b/ig, " "));
  }

  function manualChartType(question, fallback) {
    return requestedChartType(question) || fallback;
  }

  function manualChartLabelsForType(type, labels) {
    labels = labels || [];
    return type === "scatter" ? labels.map(function (_, index) { return index + 1; }) : labels;
  }

  function manualChartCaption(type, originalLabels, baseCaption) {
    if (type !== "scatter") return baseCaption;
    return baseCaption + " Scatter chart was explicitly requested; X-axis positions map to groups in table order: " + originalLabels.map(function (label, index) { return (index + 1) + " = " + label; }).join("; ") + ".";
  }

  function asksForDistribution(question) {
    var q = String(question || "");
    return /\b(share|mix|distribution|frequency|count|counts|how many|number of|percentage|percent|usage|most\s+used|least\s+used|extreme\s+used)\b/i.test(q) || (/\border\s+wise\b/i.test(q) && /\b(payment|method|category|city|department|zone|status|type)\b/i.test(q));
  }

  function asksForSort(question) {
    var q = String(question || "");
    if (asksForDistribution(q)) return false;
    if (/\border\s+wise\b/i.test(q) && !/\b(low\s+to\s+high|high\s+to\s+low|lowest|highest|ascending|descending|rank|ranking|sort|sorted|ordered)\b/i.test(q)) return false;
    return /\b(low\s+to\s+high|high\s+to\s+low|lowest|highest|ascending|descending|rank|ranking|sort|sorted|order|ordered)\b/i.test(q);
  }

  function asksForTimeBreakdown(question) {
    var q = String(question || "")
      .replace(/\bnot\s+by\s+date\b/ig, "")
      .replace(/\bwithout\s+date\b/ig, "")
      .replace(/\bexclude\s+date\b/ig, "")
      .replace(/\bnot\s+by\s+month\b/ig, "")
      .replace(/\bwithout\s+month\b/ig, "")
      .replace(/\bexclude\s+month\b/ig, "");
    return /\b(each\s+month|all\s+months|monthly|monthwise|month-wise|month\s+wise|by\s+month|quarterly|quarter-wise|quarter\s+wise|by\s+quarter|yearly|annual|yearwise|year-wise|year\s+wise|by\s+year|date\s+wise|date-wise|by\s+date|over\s+time|time\s+series)\b/i.test(q);
  }

  function countSortAscending(question) {
    var q = String(question || "");
    if (/\b(lowest\s+to\s+highest|low\s+to\s+high|ascending|asc)\b/i.test(q)) return true;
    if (/\b(highest\s+to\s+lowest|high\s+to\s+low|descending|desc)\b/i.test(q)) return false;
    return false;
  }

  function forbiddenColumns(metadata, question) {
    metadata = metadata || {};
    var q = columnKey(question);
    var text = String(question || "");
    return (metadata.headings || []).filter(function (heading) {
      var key = columnKey(heading);
      if (!key) return false;
      var raw = String(heading || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
      if (new RegExp("\\b(?:not|without|exclude|avoid)\\s+(?:by\\s+)?`?" + raw + "`?\\b", "i").test(text)) return true;
      return q.indexOf("notby" + key) >= 0 || q.indexOf("without" + key) >= 0 || q.indexOf("exclude" + key) >= 0 || q.indexOf("avoid" + key) >= 0;
    });
  }

  function questionExplicitlyExcludesColumn(question, column) {
    var q = String(question || "");
    var columnText = String(column || "").trim();
    if (!q || !columnText) return false;
    var key = columnKey(columnText);
    var aliases = [columnText, key].filter(Boolean);
    if (key === "orderid") aliases.push("order id", "orderid");
    if (key === "customer") aliases.push("customer name", "customer");
    var lowerQuestion = q.toLowerCase();
    var normalizedQuestion = columnKey(q);
    if (normalizedQuestion.indexOf("notby" + key) >= 0 || normalizedQuestion.indexOf("without" + key) >= 0 || normalizedQuestion.indexOf("exclude" + key) >= 0 || normalizedQuestion.indexOf("avoid" + key) >= 0) return true;
    return q.split(/[.;!?]/).some(function (part) {
      if (!/\b(not|do\s+not|don't|without|exclude|excluding|avoid|ignore)\b/i.test(part)) return false;
      return aliases.some(function (alias) {
        if (!alias) return false;
        var raw = String(alias).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
        return new RegExp("\\b" + raw + "\\b", "i").test(part);
      });
    }) || aliases.some(function (alias) {
      var raw = String(alias).toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
      return new RegExp("\\b(?:not|do\\s+not|don't|without|exclude|excluding|avoid|ignore)\\b[^.;!?]{0,120}\\b" + raw + "\\b", "i").test(lowerQuestion);
    });
  }

  function isForbiddenColumn(metadata, question, column) {
    var key = columnKey(column);
    return forbiddenColumns(metadata, question).some(function (heading) { return columnKey(heading) === key; });
  }

  function flattenBlockText(blocks, options) {
    options = Object.assign({ tableRows: 12, maxChars: 50000 }, options || {});
    var parts = [];
    var length = 0;
    function add(value) {
      if (length >= options.maxChars) return;
      if (value == null) return;
      if (Array.isArray(value)) value.forEach(add);
      else if (typeof value === "object") Object.keys(value).forEach(function (key) { add(value[key]); });
      else {
        var text = String(value);
        length += text.length + 1;
        parts.push(text);
      }
    }
    function addBlock(block) {
      if (!block || typeof block !== "object") return;
      add(block.type);
      var data = block.data || {};
      if (block.type === "table" && Array.isArray(data.rows)) {
        add((data.rows[0] || []).join(" "));
        data.rows.slice(1, Math.max(1, options.tableRows + 1)).forEach(function (row) { add(row); });
        add("table row count " + Math.max(0, data.rows.length - 1));
        return;
      }
      if (block.type === "toggle" && Array.isArray(data.blocks)) {
        add(data.title || "");
        data.blocks.forEach(addBlock);
        return;
      }
      if (block.type === "chart") {
        add([data.type, data.title, data.seriesLabels, data.xAxisLabel, data.yAxisLabel, data.caption].join(" "));
        return;
      }
      add(data);
    }
    (Array.isArray(blocks) ? blocks : []).forEach(addBlock);
    return parts.join(" ").toLowerCase();
  }

  function nestedTables(blocks) {
    var tables = [];
    function walk(list) {
      (Array.isArray(list) ? list : []).forEach(function (block) {
        if (!block || typeof block !== "object") return;
        if (block.type === "table") tables.push(block);
        var data = block.data || {};
        if (Array.isArray(data.blocks)) walk(data.blocks);
      });
    }
    walk(blocks);
    return tables;
  }

  function tableHeadersText(tables) {
    return (tables || []).map(function (table) {
      var rows = table && table.data && Array.isArray(table.data.rows) ? table.data.rows : [];
      return (rows[0] || []).join(" ");
    }).join(" ").toLowerCase();
  }

  function tableRowCount(tables) {
    return (tables || []).reduce(function (sum, table) {
      var rows = table && table.data && Array.isArray(table.data.rows) ? table.data.rows : [];
      return sum + Math.max(0, rows.length - 1);
    }, 0);
  }

  function duplicateDetailIntent(question) {
    var q = String(question || "");
    return /\bduplicates?|duplicate\s+rows?\b/i.test(q) &&
      /\b(row\s*#|row\s+numbers?|row\s+no\.?|row\s+ids?|row\s+indexes?|row\s+indices|which\s+rows?|give\s+(?:me\s+)?rows?|show\s+(?:me\s+)?rows?|list\s+(?:the\s+)?rows?|test[_\s-]*id|ids?)\b/i.test(q);
  }

  function duplicateCountIntent(question) {
    var q = String(question || "");
    return /\b(?:exact\s+)?duplicates?\b|\b(?:exact\s+)?duplicate\s+rows?\b/i.test(q) && !duplicateDetailIntent(q);
  }

  function missingValuesIntent(question) {
    return /\b(missing|blank|null|empty)\b/i.test(String(question || "")) &&
      /\b(values?|column-wise|columns?|fields?|data)\b/i.test(String(question || ""));
  }

  function strictPromptColumn(metadata, phrase) {
    metadata = metadata || {};
    phrase = String(phrase || "")
      .replace(/[`'"]/g, "")
      .replace(/\b(?:the|a|an|name|names|column|columns|field|fields|value|values|data|wise|basis|based|give|show|return|include|with|and|by|per|each|for|group|grouped|breakdown|break\s*down)\b/ig, " ")
      .replace(/\s+/g, " ")
      .trim();
    if (!phrase) return "";
    var phraseKey = columnKey(phrase);
    var headings = metadata.headings || [];
    var exact = headings.filter(function (heading) { return columnKey(heading) === phraseKey; })[0];
    if (exact) return exact;
    var partial = headings.filter(function (heading) {
      var key = columnKey(heading);
      return key && phraseKey && (key.indexOf(phraseKey) >= 0 || phraseKey.indexOf(key) >= 0);
    })[0];
    if (partial) return partial;
    var tokens = phrase.split(/\s+/).filter(function (token) { return token.length > 1; });
    return findColumnByWords(metadata, [phrase].concat(tokens));
  }

  function groupedMissingValuesColumn(metadata, question) {
    var q = String(question || "");
    if (!missingValuesIntent(q)) return "";
    var patterns = [
      /\b(?:based\s+on|grouped\s+by|group\s+by|break\s*down\s+by|breakdown\s+by|by|per|for\s+each)\s+([^,.;?]+)/ig
    ];
    for (var p = 0; p < patterns.length; p += 1) {
      var match;
      while ((match = patterns[p].exec(q))) {
        var column = strictPromptColumn(metadata, match[1]);
        if (column) return column;
      }
    }
    var qKey = columnKey(q);
    var headings = metadata.headings || [];
    for (var i = 0; i < headings.length; i += 1) {
      var headingKey = columnKey(headings[i]);
      if (headingKey && qKey.indexOf(headingKey + "wise") >= 0) return headings[i];
    }
    return "";
  }

  function groupedMissingValuesRequested(question) {
    var q = String(question || "");
    if (!missingValuesIntent(q)) return false;
    if (/\b(?:based\s+on|grouped\s+by|group\s+by|break\s*down\s+by|breakdown\s+by|by|per|for\s+each)\s+[^,.;?]+/i.test(q)) return true;
    return !!groupedMissingValuesColumn({}, q);
  }

  function dateComparisonRowCountQuestion(question) {
    var q = String(question || "");
    return /\brows?\s+(?:have|where)\b/i.test(q) && /\b(before|after)\b/i.test(q);
  }

  function thresholdCountQuestion(question) {
    var q = String(question || "");
    return /\b(count|how\s+many|number\s+of|show|list|find)\b/i.test(q) &&
      (/\b(above|over|greater\s+than|more\s+than|below|under|less\s+than|fewer\s+than|at\s+least|at\s+most|no\s+more\s+than|greater\s+than\s+or\s+equal\s+to|less\s+than\s+or\s+equal\s+to|negative)\b/i.test(q) || /(?:>=|<=|>|<)\s*(?:rs\.?|inr|[\u20b9$])?\s*[0-9]/i.test(q));
  }

  function filteredAggregationQuestion(question) {
    var q = String(question || "");
    return /\b(where|only\s+for|only\s+where)\b/i.test(q) || /\bfor\s+[A-Za-z][A-Za-z0-9_ ]{1,60}\s*=/i.test(q) ?
      /\b(total|sum|average|avg|mean|median|min(?:imum)?|max(?:imum)?|count|how\s+many|number\s+of)\b/i.test(q) : false;
  }

  function filteredRowListingQuestion(question) {
    var q = String(question || "");
    if (!q || filteredAggregationQuestion(q)) return false;
    if (!/\b(where|only\s+where)\b/i.test(q) && !/\b[A-Za-z][A-Za-z0-9_ ]{1,60}\s*=\s*[^,.;?]+/i.test(q)) return false;
    return /\b(show|list|find|display|give|return)\b[\s\S]{0,100}\brows?\b/i.test(q) || /\brows?\s+where\b/i.test(q);
  }

  function rankingQuestion(question) {
    var q = String(question || "");
    if (/\b(top\s+\d+|bottom\s+\d+|rank|ranking)\b/i.test(q)) return true;
    if (/\b(which|what|find|identify|show)\b[\s\S]{0,80}\b(highest|lowest|largest|smallest)\b/i.test(q) && !/\bsorted?\s+(?:highest|lowest|ascending|descending)|highest\s+to\s+lowest|lowest\s+to\s+highest\b/i.test(q)) return true;
    return false;
  }

  function rankingIsBottom(question) {
    var q = String(question || "");
    if (/\b(highest\s+to\s+lowest|high\s+to\s+low|descending|desc|top\s+\d*|highest|largest|most)\b/i.test(q)) return false;
    return /\b(bottom\s+\d*|lowest\s+to\s+highest|low\s+to\s+high|ascending|asc|lowest|smallest|least)\b/i.test(q);
  }

  function groupedCountQuestion(question) {
    var q = String(question || "");
    return /\b(count|counts|how\s+many|number\s+of)\b/i.test(q) &&
      /\b(by|group\s+by|grouped\s+by|per|for\s+each)\b/i.test(q) &&
      !/\b(total|sum|average|avg|mean|median|min(?:imum)?|max(?:imum)?|rate|share|contribution|percentage\s+of\s+total|percent\s+of\s+total)\b/i.test(q);
  }

  function categoricalCountValueColumn(metadata, question) {
    metadata = metadata || {};
    var q = String(question || "");
    if (!/\b(count|counts|how\s+many|number\s+of)\b/i.test(q)) return "";
    if (/\b(total|sum|average|avg|mean|median|min(?:imum)?|max(?:imum)?|rate|share|contribution|percentage\s+of\s+total|percent\s+of\s+total|above|below|greater|less|over|under)\b/i.test(q)) return "";
    var categorical = metadata.categoricalColumns || [];
    var mentioned = promptMentionedColumns(metadata, q).filter(function (column) {
      return categorical.indexOf(column) !== -1 && !isForbiddenColumn(metadata, q, column);
    });
    if (mentioned.length) return mentioned[0];
    if (/\bicu\b/i.test(q)) return findColumnByWords(metadata, ["icu", "icuflag", "icu flag"]);
    if (/\breadmission|readmitted\b/i.test(q)) return findColumnByWords(metadata, ["readmission", "readmission30days", "readmitted"]);
    if (/\boutcomes?\b/i.test(q)) return findColumnByWords(metadata, ["outcome", "patient outcome"]);
    if (/\bclaim\s*status|claims?\s+status\b/i.test(q)) return findColumnByWords(metadata, ["claimstatus", "claim status"]);
    if (/\bstatus\b/i.test(q)) return findColumnByWords(metadata, ["status", "claimstatus", "qa status"]);
    return "";
  }

  function asksForScalarRate(question) {
    var q = String(question || "");
    if (/\b(share|contribution|percentage\s+of\s+total|percent\s+of\s+total)\b/i.test(q)) return false;
    return /\b(rate|percentage|percent|%)\b/i.test(q) && !executionRequestsGrouping(q) && !/\b(by|per|group(?:ed)?\s+by|for\s+each)\b/i.test(q);
  }

  function asksForGroupedRate(question) {
    var q = String(question || "");
    return /\b(rate|percentage|percent|%)\b/i.test(q) && /\b(by|per|group(?:ed)?\s+by|for\s+each)\b/i.test(q);
  }

  function rankingValueGroupColumn(metadata, question) {
    var q = String(question || "");
    var match = q.match(/\b(?:top|bottom|highest|lowest|largest|smallest)\s+(?:\d+\s+)?(.+?)\s+values?\s+by\b/i) ||
      q.match(/\b(?:top|bottom|highest|lowest|largest|smallest)\s+(?:\d+\s+)?(.+?)\s+by\b/i) ||
      q.match(/\b(?:top|bottom|highest|lowest|largest|smallest)\s+(?:\d+\s+)?(.+?)\s+on\s+[A-Za-z][A-Za-z0-9_ ]+\b/i);
    return match ? resolvePromptColumnPhrase(metadata, match[1], "category") : "";
  }

  function shareGroupColumn(metadata, question) {
    var q = String(question || "");
    var basedOnMatch = q.match(/\b(?:donut|doughnut|pie)?\s*([A-Za-z][A-Za-z0-9_ ]{1,60}?)\s+share\s+based\s+on\s+([A-Za-z][A-Za-z0-9_ ]{1,60}?)(?:\s+not\b|[?.;]|$)/i);
    if (basedOnMatch) {
      var basedOnGroup = resolvePromptColumnPhrase(metadata, basedOnMatch[1], "category");
      var basedOnMetric = resolvePromptColumnPhrase(metadata, basedOnMatch[2], "number");
      if (basedOnGroup && basedOnGroup !== basedOnMetric) return basedOnGroup;
    }
    var match = q.match(/\b(?:create|make|show|plot|draw)\b[\s\S]{0,80}?\bfor\s+(.+?)\s+(?:contribution|share|percentage\s+of\s+total|percent\s+of\s+total)\b/i) ||
      q.match(/\bshow\s+(.+?)\s+(?:contribution|share|percentage\s+of\s+total|percent\s+of\s+total)\b/i) ||
      q.match(/\b([A-Za-z][A-Za-z0-9_ ]{1,60}?)\s+(?:contribution|share)\s+of\s+total\b/i);
    if (!match) return "";
    var phrase = String(match[1] || "").replace(/\b(?:a|an|the|donut|doughnut|pie|chart|graph|for)\b/ig, " ").trim();
    return resolvePromptColumnPhrase(metadata, phrase, "category");
  }

  function implicitMetricByGroupSpec(metadata, question) {
    metadata = metadata || {};
    var q = String(question || "");
    if (!q.trim()) return null;
    var metric = manualMetricColumn(metadata, q);
    var groups = promptGroupColumns(metadata, q, "");
    if (!groups.length) {
      var wiseMatch = q.match(/\b([A-Za-z][A-Za-z0-9_ ]{1,70}?)\s+wise\b/i);
      var acrossMatch = q.match(/\bacross\s+([^,.;?]+?)(?:\s+(?:for|using|with|where|include|including|sort|from|over|only|exclude|no|please)\b|[,.;?]|$)/i);
      var phrase = wiseMatch ? wiseMatch[1] : (acrossMatch ? acrossMatch[1] : "");
      if (wiseMatch) {
        columnsMentionedInPhrase(metadata, phrase).forEach(function (column) {
          if ((metadata.categoricalColumns || []).indexOf(column) !== -1 && groups.indexOf(column) < 0) groups.push(column);
        });
      }
      var column = phrase && !groups.length ? resolvePromptColumnPhrase(metadata, phrase, "category") : "";
      if (column && groups.indexOf(column) < 0) groups.push(column);
    }
    if (!metric || !groups.length || groups[0] === metric) return null;
    return { metric: metric, groupBy: groups[0], groups: groups };
  }

  function metricByGroupQuestion(question) {
    var q = String(question || "");
    var groupingText = q.replace(/\bacross\s+(?:all\s+)?(?:(?:parsed|valid|non[-\s]?missing)\s+)?rows?\b/ig, " ");
    return (/\b(by|group(?:ed)?\s+by|per|for\s+each|wise|across|break\s*down|breakdown)\b/i.test(groupingText) || /\beach\s+[A-Za-z][A-Za-z0-9_ -]{1,60}\b/i.test(groupingText)) &&
      !/\b(missing|blank|null|empty|duplicate|unique|distinct|correlation|verify|mismatch)\b/i.test(q);
  }

  function narrativeMetricByGroupQuestion(question) {
    return /\b(what\s+stands\s+out|insights?|summari[sz]e|explain|interpret|narrative)\b/i.test(String(question || "")) &&
      /\b(across|by|per|for\s+each|wise)\b/i.test(String(question || ""));
  }

  function naturalComparisonSpec(metadata, question) {
    metadata = metadata || {};
    var q = String(question || "");
    var match = q.match(/\b(?:anything\s+weird|weird|anomal(?:y|ies|ous)|unusual)\s+in\s+(.+?)\s+for\s+(.+?)(?:[?.;]|$)/i);
    if (match) {
      var weirdMetric = resolveLoosePromptColumn(metadata, match[1], "number") || manualMetricColumn(metadata, match[1]);
      var weirdGroup = resolveLoosePromptColumn(metadata, match[2], "category");
      if (weirdMetric && weirdGroup && weirdMetric !== weirdGroup && (metadata.numericStats || {})[weirdMetric]) {
        return { metric: weirdMetric, groupBy: weirdGroup, aggregation: "average", note: "grounded_anomaly_scan" };
      }
    }
    match = q.match(/\bcompare\s+(.+?)\s+on\s+(.+?)\s+(?:tell|show|find)?\s*(?:the\s+)?(?:worst|best|highest|lowest)?\b/i);
    if (match) {
      var compareGroup = resolveLoosePromptColumn(metadata, match[1], "category");
      var compareMetric = resolveLoosePromptColumn(metadata, match[2], "number") || manualMetricColumn(metadata, match[2]);
      if (compareMetric && compareGroup && compareMetric !== compareGroup && (metadata.numericStats || {})[compareMetric]) {
        return { metric: compareMetric, groupBy: compareGroup, aggregation: "average", note: "business_direction_ambiguous" };
      }
    }
    match = q.match(/\bwhich\s+(.+?)\s+is\s+(?:best|worst|highest|lowest)\s+(?:for|by|on)\s+(.+?)(?:[?.;]|$)/i);
    if (match) {
      var bestGroup = resolveLoosePromptColumn(metadata, match[1], "category");
      var bestMetric = resolveLoosePromptColumn(metadata, match[2], "number") || manualMetricColumn(metadata, match[2]);
      if (bestMetric && bestGroup && bestMetric !== bestGroup && (metadata.numericStats || {})[bestMetric]) {
        return { metric: bestMetric, groupBy: bestGroup, aggregation: "sum", note: "superlative_metric_grounding" };
      }
    }
    return null;
  }

  function dirtySheetDiagnosticPrompt(question) {
    var q = String(question || "");
    return /\b(?:dates?\s+mixed|mixed\s+dates?|percent\s+vals?|percentage\s+formats?|0\s+rows?\s+in\s+file|zero\s+rows?|very\s+wide|220\+?\s+cols?|caps\s+lowercase|spelling\s+mistakes?|dirty\s+categor(?:y|ies)|normalize\s+parseable\s+dates?|ambiguous\s+dates?|currency\s*\/\s*text\s+numbers?|text\s+numbers?|invalid\s+tokens?)\b/i.test(q);
  }

  function classifyManualAnalyticsIntent(metadata, question) {
    metadata = metadata || {};
    var q = String(question || "");
    if (duplicateDetailIntent(q)) return { intentType: "duplicate_detail", route: "duplicateDetailBlocks", aggregation: "duplicate_row_scan", rowLevelAllowed: true };
    if (duplicateCountIntent(q)) return { intentType: "duplicate_count", route: "scalarManualCheckBlocks", aggregation: "metadata_duplicate_count", rowLevelAllowed: false };
    if (/\bpredict|forecast|forecasting\b/i.test(q) && /\bdo\s+not\s+use\s+(?:a\s+)?forecast(?:ing)?(?:\s+method)?|without\s+forecast(?:ing)?|no\s+forecast(?:ing)?\b/i.test(q)) return { intentType: "missing_column_guard", route: "missingColumnGuardBlocks", aggregation: "contradiction_guard", rowLevelAllowed: false };
    if (/^\s*(show|summari[sz]e|what\s+is|what\s+are)?\s*performance\s*\??\s*$/i.test(q)) return { intentType: "clarification", route: "clarificationBlocks", aggregation: "needs_metric_clarification", rowLevelAllowed: false };
    var scatterPriorityPair = requestedChartType(q) === "scatter" ? manualNumericPair(metadata, q) : null;
    if (scatterPriorityPair) return { intentType: "relationship_scatter", route: "manualRelationshipBlocks", aggregation: "relationship_scatter", metric: scatterPriorityPair.x + "," + scatterPriorityPair.y, xMetric: scatterPriorityPair.x, yMetric: scatterPriorityPair.y, rowLevelAllowed: true, requiresChart: true, chartType: "scatter" };
    var naturalCompare = naturalComparisonSpec(metadata, q);
    if (naturalCompare) return { intentType: "grouped_analytics", route: "groupedMetricBlocks", aggregation: naturalCompare.aggregation, metric: naturalCompare.metric, groupBy: naturalCompare.groupBy, rowLevelAllowed: false, narrativeNote: naturalCompare.note };
    if (/\b(?:same\s+(?:one|analysis|thing|result)?|same)\b/i.test(q) && !manualMetricColumn(metadata, q)) return { intentType: "clarification", route: "clarificationBlocks", aggregation: "needs_previous_context", rowLevelAllowed: false };
    if (dirtySheetDiagnosticPrompt(q)) return { intentType: "clarification", route: "clarificationBlocks", aggregation: "data_quality_diagnostic", rowLevelAllowed: false };
    if (filteredRowListingQuestion(q) && manualFilters(metadata, q).length) return { intentType: "filtered_rows", route: "filteredRowBlocks", aggregation: "filtered_rows", rowLevelAllowed: true };
    if (/\b(unique|distinct|more\s+than\s+one\s+encounter|more\s+than\s+once|appear\s+more\s+than\s+once|repeated?|repeat\s+patients?)\b/i.test(q)) {
      var idMetric = resolveLoosePromptColumn(metadata, q, "identifier") || findColumnByWords(metadata, ["patient id", "patientid", "customer id", "customerid", "id"]);
      if (idMetric) return { intentType: /\bmore\s+than\s+one\s+encounter|more\s+than\s+once|appear\s+more\s+than\s+once|repeated?|repeat\s+patients?\b/i.test(q) ? "repeat_count" : "distinct_count", route: "distinctAndRepeatBlocks", aggregation: /\bmore\s+than\s+one\s+encounter|more\s+than\s+once|appear\s+more\s+than\s+once|repeated?|repeat\s+patients?\b/i.test(q) ? "repeat_count" : "distinct_count", metric: idMetric, rowLevelAllowed: false };
    }
    if (ambiguousManualAnalyticsPrompt(metadata, q)) return { intentType: "clarification", route: "clarificationBlocks", aggregation: "needs_metric_clarification", rowLevelAllowed: false };
    if (/\b(verify|validate|check)\b/i.test(q) && /\bmismatch/i.test(q)) return { intentType: "formula_validation", route: "formulaValidationBlocks", aggregation: "formula_mismatch_count", rowLevelAllowed: false };
    if (asksForScalarRate(q)) return { intentType: "scalar_rate", route: "scalarMetricExecutionBlocks", aggregation: "rate", rowLevelAllowed: false };
    if (asksForGroupedRate(q)) return { intentType: "grouped_rate", route: "groupedRateBlocks", aggregation: "positive_rate_by_group", groupBy: promptGroupColumns(metadata, q, resolveGroupColumn(metadata, q))[0] || resolveGroupColumn(metadata, q), rowLevelAllowed: false };
    if (/\bdiscount\b/i.test(q) && !discountColumn(metadata)) return { intentType: "missing_column_guard", route: "missingColumnGuardBlocks", aggregation: "missing_metric_validation", metric: "Discount", rowLevelAllowed: false };
    var missingMetricBeforeAnalytics = missingRequestedMetric(metadata, q);
    if (missingMetricBeforeAnalytics) return { intentType: "missing_column_guard", route: "missingColumnGuardBlocks", aggregation: "missing_metric_validation", metric: missingMetricBeforeAnalytics, rowLevelAllowed: false };
    if (/\b(share|contribution)\s*(?:percentage|percent|%)?|percentage\s+of\s+total|percent\s+of\s+total\b/i.test(q)) return { intentType: "grouped_share", route: "singleContributionShareBlocks", aggregation: "share_percent", metric: manualMetricColumn(metadata, q), groupBy: shareGroupColumn(metadata, q) || promptGroupColumns(metadata, q, resolveGroupColumn(metadata, q))[0] || resolveGroupColumn(metadata, q), rowLevelAllowed: false, requiresChart: asksForGraph(q), chartType: requestedChartType(q) };
    if (/\b(q1|quartile|q3|iqr)\b/i.test(q) && !/\b(outliers?|above\s+q3)\b/i.test(q)) return { intentType: "distribution_stats", route: "distributionStatsBlocks", aggregation: "quartiles_iqr", metric: manualMetricColumn(metadata, q), rowLevelAllowed: false };
    if (/\bq3\s*\+\s*1\.5\s*(?:x|×|\*)\s*iqr|iqr\b/i.test(q)) return { intentType: "iqr_outlier_count", route: "iqrOutlierBlocks", aggregation: "q3_plus_1_5_iqr", metric: manualMetricColumn(metadata, q), rowLevelAllowed: false };
    if (dateComparisonRowCountQuestion(q)) return { intentType: "date_logic", route: "scalarManualCheckBlocks", aggregation: "date_comparison_count", rowLevelAllowed: false };
    var pivotPriority = pivotMatrixIntent(metadata, q);
    if (pivotPriority) return { intentType: pivotPriority.valueMode === "count" ? "cross_tab_count" : "cross_tab", route: "pivotMatrixBlocks", aggregation: pivotPriority.valueMode === "count" ? "count_cross_tab" : "sum_cross_tab", metric: pivotPriority.valueMode === "count" ? "__row_count" : pivotPriority.valueColumn, groupBy: (pivotPriority.rowColumns && pivotPriority.rowColumns.length ? pivotPriority.rowColumns : [pivotPriority.rowColumn]).concat([pivotPriority.columnColumn]).filter(Boolean).join(","), rowLevelAllowed: false, pivot: pivotPriority };
    if (asksForTimeBreakdown(q)) return { intentType: "time_series", route: "timeSeries", aggregation: "time_grouping", metric: manualMetricColumn(metadata, q), rowLevelAllowed: false };
    if (/\bdiscount\b/i.test(q) && !discountColumn(metadata)) return { intentType: "missing_column_guard", route: "missingColumnGuardBlocks", aggregation: "missing_metric_validation", metric: "Discount", rowLevelAllowed: false };
    if (thresholdCountQuestion(q)) return { intentType: "threshold_count", route: "thresholdCountBlocksV2", aggregation: "threshold_count", metric: manualMetricColumn(metadata, q), rowLevelAllowed: false };
    if (filteredAggregationQuestion(q)) {
      var filteredGroups = promptGroupColumns(metadata, q, "").filter(function (column) {
        return column && !isForbiddenColumn(metadata, q, column) && !manualFilters(metadata, q).some(function (filter) { return filter.column === column; });
      });
      return { intentType: "filtered_aggregation", route: "filteredAggregationBlocks", aggregation: /\b(count|how\s+many|number\s+of)\b/i.test(q) && !/\b(total|totl|tatal|sum|average|avg|avrage|averge|mean|median|min(?:imum)?|max(?:imum)?)\b/i.test(q) ? "filtered_count" : aggregationMode(q), metric: manualMetricColumn(metadata, questionWithoutManualFilterValues(metadata, q)) || manualMetricColumn(metadata, q), groupBy: filteredGroups.join(","), rowLevelAllowed: false };
    }
    var rankedMetricBeforeCount = rankingQuestion(q) ? manualMetricColumn(metadata, q) : "";
    if (rankedMetricBeforeCount && /\b(?:and|with|include|\+)\s+count\b|\bcount\s+(?:also|too)\b/i.test(q)) {
      var rankedMetricGroups = promptGroupColumns(metadata, q, rankingValueGroupColumn(metadata, q) || resolveGroupColumn(metadata, q));
      if (rankedMetricGroups.length) return { intentType: "grouped_analytics", route: "genericMultiMetricGroupBlocks", aggregation: aggregationMode(q), metric: rankedMetricBeforeCount, groupBy: rankedMetricGroups[0], rowLevelAllowed: false };
    }
    var categoricalCountColumn = categoricalCountValueColumn(metadata, q);
    if (categoricalCountColumn) return { intentType: "grouped_count", route: "groupedCountBlocks", aggregation: "count", groupBy: categoricalCountColumn, metric: "__row_count", rowLevelAllowed: false, requiresChart: asksForGraph(q), chartType: requestedChartType(q) };
    if (groupedCountQuestion(q)) {
      var countGroups = promptGroupColumns(metadata, q, resolveGroupColumn(metadata, q));
      if (countGroups.length) return { intentType: "grouped_count", route: "groupedCountBlocks", aggregation: "count", groupBy: countGroups.join(","), metric: "__row_count", rowLevelAllowed: false, requiresChart: asksForGraph(q), chartType: requestedChartType(q) };
    }
    if (/\bsame\s+analysis\b/i.test(q) && executionRequestsGrouping(q)) {
      var sameAnalysisGroups = promptGroupColumns(metadata, q, resolveGroupColumn(metadata, q));
      if (sameAnalysisGroups.length) return { intentType: "grouped_count", route: "groupedCountBlocks", aggregation: "count", groupBy: sameAnalysisGroups.join(","), metric: "__row_count", rowLevelAllowed: false, requiresChart: asksForGraph(q), chartType: requestedChartType(q) };
    }
    if (rankingQuestion(q)) {
      var rankingPriorityGroup = rankingValueGroupColumn(metadata, q);
      var rankingGroups = rankingPriorityGroup ? [rankingPriorityGroup] : promptGroupColumns(metadata, q, resolveGroupColumn(metadata, q));
      var rankingMetric = manualMetricColumn(metadata, q);
      if (rankingGroups.length && rankingMetric && aggregationMode(q) !== "count") {
        return { intentType: "ranking", route: "ranking", aggregation: aggregationMode(q), metric: rankingMetric, groupBy: rankingGroups[0], rowLevelAllowed: semanticColumnInfo(metadata, rankingGroups[0]).identifier, requiresChart: asksForGraph(q), chartType: requestedChartType(q) };
      }
      return { intentType: "ranking", route: "ranking", aggregation: "rank", metric: manualMetricColumn(metadata, q), groupBy: rankingGroups[0] || "", rowLevelAllowed: rankingGroups[0] ? semanticColumnInfo(metadata, rankingGroups[0]).identifier : false };
    }
    var narrativeSpec = narrativeMetricByGroupQuestion(q) ? implicitMetricByGroupSpec(metadata, q) : null;
    if (narrativeSpec) return { intentType: "grouped_analytics", route: "groupedMetricBlocks", aggregation: "sum", metric: narrativeSpec.metric, groupBy: narrativeSpec.groupBy, rowLevelAllowed: false };
    var implicitSpec = metricByGroupQuestion(q) ? implicitMetricByGroupSpec(metadata, q) : null;
    if (implicitSpec) return { intentType: "grouped_analytics", route: "groupedMetricBlocks", aggregation: aggregationMode(q), metric: implicitSpec.metric, groupBy: implicitSpec.groupBy, rowLevelAllowed: false };
    var nonMissingScalarIntent = /\b(total|totl|tatal|sum|average|avg|avrage|averge|mean|median|min(?:imum)?|max(?:imum)?)\b/i.test(q) ? scalarAnalyticsIntent(metadata, q) : null;
    if (nonMissingScalarIntent) return { intentType: "scalar_metric", route: "scalarMetricExecutionBlocks", aggregation: aggregationMode(q), rowLevelAllowed: false, metric: manualMetricColumn(metadata, q) };
    if (isMetadataManualQuestion(q, metadata)) return { intentType: "metadata_summary", route: "scalarManualCheckBlocks", aggregation: "metadata_lookup", rowLevelAllowed: false };
    var earlyScalarIntent = /\b(total|totl|tatal|sum|average|avg|avrage|averge|mean|median|min(?:imum)?|max(?:imum)?)\b/i.test(q) ? scalarAnalyticsIntent(metadata, q) : null;
    if (earlyScalarIntent) return { intentType: "scalar_metric", route: "scalarMetricExecutionBlocks", aggregation: aggregationMode(q), rowLevelAllowed: false, metric: manualMetricColumn(metadata, q) };
    var groupedMissingColumn = groupedMissingValuesColumn(metadata, q);
    if (groupedMissingColumn) return { intentType: "grouped_missing_values", route: "groupedMissingValuesBlocks", aggregation: "missing_count_by_group", groupBy: groupedMissingColumn, rowLevelAllowed: false };
    if (missingValuesIntent(q) && /\b(values?|column-wise|columns?|fields?|every|all|each)\b/i.test(q)) return { intentType: "missing_values", route: "scalarManualCheckBlocks", aggregation: "metadata_missing_count", rowLevelAllowed: false };
    if (groupedMissingValuesRequested(q)) return { intentType: "missing_values", route: "scalarManualCheckBlocks", aggregation: "metadata_missing_count", rowLevelAllowed: false };
    if (dateComparisonRowCountQuestion(q)) return { intentType: "date_logic", route: "scalarManualCheckBlocks", aggregation: "date_comparison_count", rowLevelAllowed: false };
    if (/\b(rows?\s+and\s+columns?|columns?\s+and\s+rows?|sheet\s+shape|dataset\s+shape|how\s+many\s+columns|how\s+many\s+rows)\b/i.test(q) && /\b(missing|blank|null|empty)\b/i.test(q)) return { intentType: "metadata_mixed", route: "metadataMixedBlocks", aggregation: "metadata_shape_and_missing", rowLevelAllowed: false };
    if (scalarAnalyticsIntent(metadata, q)) return { intentType: "scalar_metric", route: "scalarMetricExecutionBlocks", aggregation: aggregationMode(q), rowLevelAllowed: false, metric: manualMetricColumn(metadata, q) };
    if (/\b(correlation|correlate|pearson)\b/i.test(q)) return { intentType: "correlation", route: "correlationBlocks", aggregation: "pearson_correlation", rowLevelAllowed: false };
    if (/\b(q1|quartile|q3|iqr)\b/i.test(q) && !/\b(outliers?|above\s+q3)\b/i.test(q)) return { intentType: "distribution_stats", route: "distributionStatsBlocks", aggregation: "quartiles_iqr", metric: manualMetricColumn(metadata, q), rowLevelAllowed: false };
    if (/\bq3\s*\+\s*1\.5\s*[x×*]\s*iqr|iqr\b/i.test(q)) return { intentType: "iqr_outlier_count", route: "iqrOutlierBlocks", aggregation: "q3_plus_1_5_iqr", rowLevelAllowed: false };
    if (/\b(insurer|insurance\s+provider|provider|payer)\b/i.test(q) && /\bapproved|pending|rejected|claim\b/i.test(q) && /\b(claim\s*status|approved\s*\/\s*pending|approved\s*,\s*pending|pending|rejected|status\s+buckets?|bucket|buckets|stored)\b/i.test(q)) return { intentType: "claim_status_provider_buckets", route: "claimStatusProviderBucketBlocks", aggregation: "status_bucket_sum", rowLevelAllowed: false };
    if (/\bmore\s+than\s+one\s+encounter|more\s+than\s+once|appear\s+more\s+than\s+once|repeated?|repeat\s+patients?\b/i.test(q)) return { intentType: "repeat_count", route: "distinctAndRepeatBlocks", aggregation: "repeat_count", rowLevelAllowed: false };
    if (/\bunique|distinct\b/i.test(q)) return { intentType: "distinct_count", route: "distinctAndRepeatBlocks", aggregation: "distinct_count", rowLevelAllowed: false };
    if (!executionRequestsGrouping(q) && filteredAggregationQuestion(q)) return { intentType: "filtered_aggregation", route: "filteredAggregationBlocks", aggregation: /\b(count|how\s+many|number\s+of)\b/i.test(q) ? "filtered_count" : aggregationMode(q), metric: manualMetricColumn(metadata, q), rowLevelAllowed: false };
    if (thresholdCountQuestion(q)) return { intentType: "threshold_count", route: "thresholdCountBlocksV2", aggregation: "threshold_count", metric: manualMetricColumn(metadata, q), rowLevelAllowed: false };
    if (/\b(share|contribution)\s*(?:percentage|percent|%)?|percentage\s+of\s+total|percent\s+of\s+total\b/i.test(q)) return { intentType: "grouped_share", route: "singleContributionShareBlocks", aggregation: "share_percent", metric: manualMetricColumn(metadata, q), groupBy: shareGroupColumn(metadata, q) || promptGroupColumns(metadata, q, resolveGroupColumn(metadata, q))[0] || resolveGroupColumn(metadata, q), rowLevelAllowed: false, requiresChart: asksForGraph(q), chartType: requestedChartType(q) };
    if (/\b(rate|percentage|percent|%)\b/i.test(q) && executionRequestsGrouping(q) && !(/\bvs\b|\bversus\b|\bnon[-\s]?\w+/i.test(q))) return { intentType: "grouped_rate", route: "groupedRateBlocks", aggregation: "positive_rate_by_group", groupBy: resolveGroupColumn(metadata, q), rowLevelAllowed: false };
    if (/\b(avg|average|avrage|averge|mean|total|totl|tatal|sum)\b/i.test(q) && (/\bvs\b|\bversus\b|\bnon[-\s]?\w+/i.test(q))) return { intentType: "vs_comparison", route: "averageByFlagComparisonBlocks", aggregation: aggregationMode(q) + "_by_flag", metric: manualMetricColumn(metadata, q), groupBy: binaryRateComparisonColumn(metadata, q, ""), rowLevelAllowed: false };
    if (/\b(rate|percentage|percent|%)\b/i.test(q) && (/\bvs\b|\bversus\b|\bnon[-\s]?\w+/i.test(q))) return { intentType: "vs_comparison", route: "binaryRateComparisonBlocks", aggregation: "rate_by_flag", rowLevelAllowed: false };
    var pivotEarly = pivotMatrixIntent(metadata, q);
    if (pivotEarly) return { intentType: pivotEarly.valueMode === "count" ? "cross_tab_count" : "cross_tab", route: "pivotMatrixBlocks", aggregation: pivotEarly.valueMode === "count" ? "count_cross_tab" : "sum_cross_tab", metric: pivotEarly.valueMode === "count" ? "__row_count" : pivotEarly.valueColumn, groupBy: (pivotEarly.rowColumns && pivotEarly.rowColumns.length ? pivotEarly.rowColumns : [pivotEarly.rowColumn]).concat([pivotEarly.columnColumn]).filter(Boolean).join(","), rowLevelAllowed: false, pivot: pivotEarly };
    if (executionRequestsGrouping(q) && !asksForTimeBreakdown(q) && !pivotMatrixIntent(metadata, q)) {
      var groupedMetric = manualMetricColumn(metadata, q);
      var groupedColumn = resolveGroupColumn(metadata, q);
      var groupedMode = aggregationMode(q);
      if (groupedMode === "count" && groupedColumn) {
        return { intentType: "grouped_count", route: "groupedCountBlocks", aggregation: "count", groupBy: groupedColumn, metric: "__row_count", rowLevelAllowed: false };
      }
      if (groupedMetric && groupedColumn && groupedMetric !== groupedColumn) {
        return { intentType: "grouped_analytics", route: "groupedMetricBlocks", aggregation: groupedMode, groupBy: groupedColumn, metric: groupedMetric, rowLevelAllowed: false };
      }
    }
    if (asksForTimeBreakdown(q)) return { intentType: "time_series", route: "timeSeries", aggregation: "time_grouping", rowLevelAllowed: false };
    if (/\b(highest|lowest|top|bottom|rank|ranking)\b/i.test(q)) return { intentType: "ranking", route: "ranking", aggregation: "rank", rowLevelAllowed: false };
    if (executionRequestsGrouping(q)) return { intentType: "grouped_analytics", route: "grouped", aggregation: "grouped", rowLevelAllowed: false };
    return null;
  }

  function directManualIntent(metadata, question) {
    return classifyManualAnalyticsIntent(metadata, question);
  }

  function manualIntentContract(metadata, question) {
    metadata = metadata || {};
    var q = String(question || "");
    var pivotIntent = pivotMatrixIntent(metadata, q);
    var comboIntent = combinationRankingIntent(metadata, q);
    var formulaIntent = formulaRatioIntent(metadata, q);
    var pair = manualNumericPair(metadata, q);
    var contract = {
      intentType: "general",
      chartType: requestedChartType(q),
      requiresChart: asksForGraph(q),
      requiresTable: /\b(table|data|show|tell|list|count|percentage|percent|share|mix|distribution|wise|by)\b/i.test(q),
      groupBy: "",
      metric: "",
      xMetric: "",
      yMetric: "",
      aggregation: "",
      percentageRequired: /\b(share|percentage|percent|mix|distribution)\b/i.test(q),
      rowLevelAllowed: true,
      forbidden: [],
      timeGroupBy: "",
      mentionedNumeric: mentionedNumericColumns(metadata, q),
      deterministicRoute: "",
      forbiddenColumns: forbiddenColumns(metadata, q)
    };
    var directIntent = directManualIntent(metadata, q);
    if (directIntent) {
      contract.intentType = directIntent.intentType;
      contract.metric = directIntent.metric || "";
      contract.groupBy = directIntent.groupBy || "";
      contract.aggregation = directIntent.aggregation || "deterministic";
      contract.rowLevelAllowed = !!directIntent.rowLevelAllowed;
      contract.requiresTable = true;
      contract.requiresChart = !!directIntent.requiresChart;
      contract.chartType = directIntent.chartType || "";
      contract.deterministicRoute = directIntent.route;
      if (directIntent.pivot) contract.pivot = directIntent.pivot;
      if (contract.intentType === "correlation" && pair) {
        contract.xMetric = pair.x;
        contract.yMetric = pair.y;
      }
    } else if (/\b(outlier|unusually|top\s+\d+|highest|largest)\b/i.test(q) && /\bdiscount\b/i.test(q)) {
      contract.intentType = "outlier_rows";
      contract.metric = discountColumn(metadata) || manualMetricColumn(metadata, q);
      contract.aggregation = "top_rows";
      contract.rowLevelAllowed = true;
      contract.requiresTable = true;
      contract.deterministicRoute = "outlierRowBlocks";
    } else if (pivotIntent) {
      contract.intentType = "pivot_matrix";
      contract.groupBy = pivotIntent.rowColumn === "__month__" ? "Month" : pivotIntent.rowColumn;
      contract.metric = pivotIntent.valueMode === "count" ? "Order Count" : pivotIntent.valueColumn;
      contract.aggregation = pivotIntent.valueMode === "count" ? "count" : "sum";
      contract.rowLevelAllowed = false;
      contract.requiresTable = true;
      contract.requiresChart = !isPivotTableOnlyPrompt(q) && /\b(stacked|comparison|chart|graph|plot|visual|visualize)\b/i.test(analyticsChartIntentText(q));
      contract.chartType = contract.requiresChart ? contract.chartType : "";
      contract.deterministicRoute = "pivotMatrixBlocks";
      contract.pivot = pivotIntent;
    } else if (comboIntent) {
      contract.intentType = "combination_ranking";
      contract.groupBy = comboIntent.columns.join(" + ");
      contract.metric = comboIntent.sortMetric;
      contract.aggregation = "sum_then_rank";
      contract.rowLevelAllowed = false;
      contract.requiresTable = true;
      contract.deterministicRoute = "combinationRankingBlocks";
      contract.combination = comboIntent;
    } else if (formulaIntent) {
      contract.intentType = "formula_metric";
      contract.metric = formulaIntent.chartMetric || "Formula Metric";
      contract.groupBy = formulaIntent.groupLabel || "";
      contract.aggregation = "ratio_of_sums";
      contract.rowLevelAllowed = false;
      contract.requiresTable = true;
      contract.deterministicRoute = "formulaRatioBlocks";
      contract.formula = formulaIntent;
    } else if (/\b(increased|decreased|increase|decrease|compared\s+with\s+the\s+previous|previous\s+month|month\s+over\s+month|mom)\b/i.test(q) && /\brevenue\b/i.test(q) && /\bprofit\b/i.test(q)) {
      contract.intentType = "month_over_month_condition";
      contract.metric = "Revenue,Profit";
      contract.groupBy = (metadata.dateColumns || [])[0] || "";
      contract.aggregation = "month_over_month_change";
      contract.rowLevelAllowed = false;
      contract.requiresTable = true;
      contract.deterministicRoute = "monthOverMonthConditionBlocks";
    } else if (/\b(rows?\s+and\s+columns?|columns?\s+and\s+rows?|(?:row|rows?|record|records?)\s+count\s+and\s+(?:column|columns?|field|fields?)\s+count|(?:column|columns?|field|fields?)\s+count\s+and\s+(?:row|rows?|record|records?)\s+count|sheet\s+shape|dataset\s+shape|how\s+many\s+columns|how\s+many\s+rows)\b/i.test(q)) {
      contract.intentType = "sheet_shape";
      contract.aggregation = "metadata_count";
      contract.rowLevelAllowed = false;
      contract.requiresTable = true;
      contract.requiresChart = false;
      contract.chartType = "";
      contract.groupBy = "";
      contract.metric = "";
      contract.deterministicRoute = "scalarManualCheckBlocks";
    } else if (/\b(?:exact\s+)?duplicate\s+rows?|duplicates?\b/i.test(q)) {
      contract.intentType = "duplicate_count";
      contract.aggregation = "metadata_duplicate_count";
      contract.rowLevelAllowed = false;
      contract.requiresTable = true;
      contract.requiresChart = false;
      contract.chartType = "";
      contract.groupBy = "";
      contract.metric = "";
      contract.deterministicRoute = "scalarManualCheckBlocks";
    } else if (/\b(values?|categories?|distribution|counts?)\b/i.test(q) && /\b(blanks?|missing|null|empty)\b/i.test(q) && (function () {
      var column = resolveQuestionColumn(metadata, q, "category");
      if (!column || columnKey(q).indexOf(columnKey(column)) < 0) return false;
      contract.intentType = "distribution_share";
      contract.groupBy = column;
      contract.aggregation = "count";
      contract.metric = "order_count";
      contract.rowLevelAllowed = false;
      contract.requiresTable = true;
      contract.requiresChart = asksForGraph(q);
      contract.chartType = contract.requiresChart ? contract.chartType : "";
      contract.deterministicRoute = "exactCountBlocks";
      return true;
    })()) {
    } else if (!executionRequestsGrouping(q) && !asksForTimeBreakdown(q) && !pivotMatrixIntent(metadata, q) && !combinationRankingIntent(metadata, q) && /\b(total|totl|tatal|sum|average|avg|avrage|averge|mean|median|min(?:imum)?|max(?:imum)?|rate|percentage|percent|how\s+many|count)\b/i.test(q) && manualMetricColumn(metadata, q)) {
      contract.intentType = /\b(rate|percentage|percent|%)\b/i.test(q) ? "scalar_rate" : (/\b(count|how\s+many)\b/i.test(q) ? "scalar_count" : "scalar_metric");
      contract.metric = manualMetricColumn(metadata, q);
      contract.aggregation = aggregationMode(q);
      contract.rowLevelAllowed = false;
      contract.requiresTable = true;
      contract.requiresChart = false;
      contract.chartType = "";
      contract.groupBy = "";
      contract.deterministicRoute = "scalarMetricExecutionBlocks";
    } else if (/\b(missing|blank|null|empty)\b/i.test(q) && /\b(values?|column-wise|columns?|fields?)\b/i.test(q)) {
      contract.intentType = "missing_values";
      contract.aggregation = "metadata_missing_count";
      contract.rowLevelAllowed = false;
      contract.requiresTable = true;
      contract.requiresChart = false;
      contract.chartType = "";
      contract.groupBy = "";
      contract.metric = "";
      contract.deterministicRoute = "scalarManualCheckBlocks";
    } else if (!executionRequestsGrouping(q) && !asksForTimeBreakdown(q) && !pivotMatrixIntent(metadata, q) && !combinationRankingIntent(metadata, q) && /\b(total|totl|tatal|sum|average|avg|avrage|averge|mean|min(?:imum)?|max(?:imum)?|rate|percentage|percent|how\s+many|count)\b/i.test(q)) {
      contract.intentType = /\b(rate|percentage|percent|%)\b/i.test(q) ? "scalar_rate" : (/\b(count|how\s+many)\b/i.test(q) ? "scalar_count" : "scalar_metric");
      contract.metric = manualMetricColumn(metadata, q);
      contract.aggregation = aggregationMode(q);
      contract.rowLevelAllowed = false;
      contract.requiresTable = true;
      contract.requiresChart = false;
      contract.chartType = "";
      contract.groupBy = "";
      contract.deterministicRoute = "scalarMetricExecutionBlocks";
    } else if (asksForDistribution(q)) {
      contract.intentType = "distribution_share";
      contract.groupBy = resolveQuestionColumn(metadata, q, "category");
      contract.timeGroupBy = asksForTimeBreakdown(q) ? ((metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "month", "created_at", "updated_at"])) : "";
      contract.aggregation = "count";
      contract.metric = "order_count";
      contract.rowLevelAllowed = false;
      contract.deterministicRoute = contract.timeGroupBy ? "monthlyDistributionBlocks" : "exactCountBlocks";
      contract.forbidden.push("raw row listing", "OrderID row sorting", "top 20 order rows");
    } else if (contract.chartType === "scatter" && pair) {
      contract.intentType = "relationship_scatter";
      contract.xMetric = pair.x;
      contract.yMetric = pair.y;
      contract.groupBy = manualRelationshipGroupColumn(metadata, q);
      contract.aggregation = contract.groupBy ? (/\b(avg|average|mean|per\s+order|per\s+row)\b/i.test(q) ? "average" : "sum") : "row";
      contract.rowLevelAllowed = !contract.groupBy;
      contract.deterministicRoute = "manualRelationshipBlocks";
    } else if (asksForSort(q)) {
      contract.intentType = "rank_sort";
      contract.groupBy = resolveGroupColumn(metadata, q);
      contract.metric = resolveMetricColumn(metadata, q);
      contract.aggregation = "sort";
      contract.deterministicRoute = "sortedGroupBlocks";
    } else if (asksForTrend(q) || /\b(month|monthly|over\s+time|time\s+series)\b/i.test(q)) {
      contract.intentType = "time_trend";
      contract.metric = (mentionedNumericColumns(metadata, q).length ? mentionedNumericColumns(metadata, q) : [manualMetricColumn(metadata, q)]).join(",");
      contract.groupBy = (metadata.dateColumns || [])[0] || "";
      contract.aggregation = "sum_by_time";
      contract.deterministicRoute = "timeTrendMetricBlocks";
    } else if (derivedMetricSpec(metadata, q)) {
      contract.intentType = "formula_metric";
      contract.metric = derivedMetricSpec(metadata, q).name;
      contract.groupBy = executionRequestsGrouping(q) ? resolveGroupColumn(metadata, q) : "";
      contract.aggregation = /\b(avg|average|mean)\b/i.test(q) ? "average" : "sum";
      contract.deterministicRoute = "derivedMetricBlocks";
    } else if (executionRequestsGrouping(q) && /\b(by|wise|group|aggregate|sum|average)\b/i.test(q)) {
      contract.intentType = "grouped_metric";
      contract.groupBy = resolveGroupColumn(metadata, q);
      contract.metric = manualMetricColumn(metadata, q);
      contract.aggregation = /\b(avg|average|mean)\b/i.test(q) ? "average" : "sum";
      contract.deterministicRoute = "groupedMetricBlocks";
    }
    return contract;
  }

  function promptBlocksGrouping(question) {
    return /\b(?:do\s+not|don't|without|no)\s+(?:group|grouping|break\s*down|breakdown)\s+by\b|\bdo\s+not\s+group\b|\bno\s+grouping\b/i.test(String(question || ""));
  }

  function executionRequestsGrouping(question) {
    var q = String(question || "");
    if (promptBlocksGrouping(q)) return false;
    var groupingText = q.replace(/\bacross\s+(?:all\s+)?(?:(?:parsed|valid|non[-\s]?missing)\s+)?rows?\b/ig, " ");
    if (/\b(matrix|pivot|heatmap|rows?\s+are|columns?\s+are|rows?\s+should\s+be|columns?\s+should\s+be|top\s+\d+|bottom\s+\d+|rank|ranking|distribution|share|mix|break\s*down|breakdown|group(?:ed)?\s+by|split\s+by|compare\s+by|for\s+each|wise|across)\b|\beach\s+[A-Za-z][A-Za-z0-9_ -]{1,60}\b/i.test(groupingText)) return true;
    if (/\bby\s+[`'"]?[A-Za-z][A-Za-z0-9_ -]{1,60}\b/i.test(groupingText)) return true;
    return false;
  }

  var ANALYTICS_RULE_SEVERITY = { BLOCKING: "blocking", WARNING: "warning", INFO: "info" };
  var SHEET_FORMULA_REGISTRY = {
    revenue_sum: {
      label: "Revenue",
      formula: "SUM(Revenue)",
      required: ["revenue"]
    },
    profit_sum: {
      label: "Profit",
      formula: "SUM(Profit)",
      required: ["profit"]
    },
    discount_sum: {
      label: "Discount",
      formula: "SUM(Discount)",
      required: ["discount"]
    },
    order_count: {
      label: "Order Count",
      formula: "COUNT_DISTINCT(OrderID) when an order identifier exists, otherwise COUNT(rows)",
      required: []
    },
    profit_share_pct: {
      label: "Profit Share %",
      formula: "group SUM(Profit) / grand total SUM(Profit) x 100",
      required: ["profit"]
    },
    profit_margin_pct: {
      label: "Profit Margin %",
      formula: "SUM(Profit) / SUM(Revenue) x 100",
      required: ["profit", "revenue"]
    },
    discount_pct: {
      label: "Discount %",
      formula: "SUM(Discount) / SUM(Revenue) x 100",
      required: ["discount", "revenue"]
    },
    net_revenue: {
      label: "Net Revenue after Discount",
      formula: "SUM(Revenue) - SUM(Discount)",
      required: ["revenue", "discount"]
    },
    revenue_per_unit: {
      label: "Average Revenue per Unit",
      formula: "SUM(Revenue) / SUM(Units Sold or Quantity)",
      required: ["revenue", "quantity"]
    },
    aov: {
      label: "Average Order Value",
      formula: "SUM(Revenue) / COUNT_DISTINCT(OrderID) when an order identifier exists, otherwise SUM(Revenue) / COUNT(rows)",
      required: ["revenue"]
    },
    revenue_share_pct: {
      label: "Revenue Share %",
      formula: "group SUM(Revenue) / grand total SUM(Revenue) x 100",
      required: ["revenue"]
    },
    units_share_pct: {
      label: "Units Share %",
      formula: "group SUM(Units Sold or Quantity) / grand total SUM(Units Sold or Quantity) x 100",
      required: ["quantity"]
    },
    period_change: {
      label: "Period Change",
      formula: "current period metric - previous period metric",
      required: []
    },
    custom_ratio: {
      label: "Requested Ratio",
      formula: "Use the exact numerator and denominator requested by the prompt",
      required: []
    }
  };
  var DOMAIN_KNOWLEDGE_PACKS = {
    ecommerce: {
      aliases: ["order", "orders", "transaction", "product", "sku", "payment", "discount", "profit", "cart", "customer", "revenue", "sales", "channel", "region"],
      commonMetrics: ["Revenue", "Profit", "Discount", "Quantity", "Average Order Value", "Profit Margin %"]
    },
    pharma: {
      aliases: ["drug", "medicine", "dosage", "manufacturer", "units", "region", "representative", "customer type", "recalled", "qa status", "expiry", "inventory", "temperature", "supplier"],
      commonMetrics: ["Revenue", "Profit", "COGS", "Units Sold", "Return Quantity", "Average Revenue per Unit"]
    },
    healthcare: {
      aliases: ["patient", "encounter", "admission", "discharge", "department", "diagnosis", "procedure", "billing", "claim", "insurance", "icu", "readmission", "mortality", "outcome", "wait time", "length of stay", "satisfaction"],
      commonMetrics: ["Total Bill", "Treatment Cost", "Medication Cost", "Lab Cost", "Insurance Claim", "Length Of Stay", "Wait Time", "Satisfaction Score"]
    },
    finance: {
      aliases: ["account", "transaction", "amount", "balance", "debit", "credit", "expense", "income", "revenue", "profit", "loss", "margin", "portfolio", "asset", "liability"],
      commonMetrics: ["Amount", "Balance", "Revenue", "Expense", "Profit", "Margin %"]
    },
    sales: {
      aliases: ["lead", "opportunity", "deal", "pipeline", "quota", "sales", "revenue", "region", "rep", "account", "stage", "conversion"],
      commonMetrics: ["Revenue", "Deal Value", "Units Sold", "Conversion Rate", "Win Rate"]
    },
    logistics: {
      aliases: ["shipment", "warehouse", "stock", "zone", "delivery", "restock", "inventory", "carrier", "route", "delay", "fulfillment", "transit", "freight"],
      commonMetrics: ["Stock Level", "Order Count", "Fulfillment Rate", "Delay", "Transit Time", "Shipping Cost"]
    },
    hr: {
      aliases: ["employee", "department", "salary", "attrition", "performance", "hire", "tenure", "role", "manager", "rating", "attendance"],
      commonMetrics: ["Employee Count", "Average Salary", "Attrition Rate", "Average Tenure", "Performance Rating"]
    },
    marketing: {
      aliases: ["campaign", "channel", "lead", "conversion", "spend", "impression", "click", "ctr", "cpc", "roi", "roas", "source", "medium"],
      commonMetrics: ["Spend", "Revenue", "Conversion Rate", "ROI"]
    },
    education: {
      aliases: ["student", "course", "class", "grade", "score", "attendance", "enrollment", "teacher", "exam", "assignment"],
      commonMetrics: ["Student Count", "Average Score", "Attendance Rate", "Pass Rate"]
    },
    real_estate: {
      aliases: ["property", "listing", "rent", "price", "area", "sqft", "bedroom", "location", "occupancy", "lease"],
      commonMetrics: ["Price", "Rent", "Area", "Occupancy Rate"]
    },
    legal_case_management: {
      aliases: ["case", "caseid", "filed", "filed date", "case type", "court", "jurisdiction", "lawyer", "case status", "legal cost", "claim value", "hearing", "settlement", "settlement amount", "case age"],
      commonMetrics: ["Legal Cost", "Claim Value", "Case Age", "Hearing Count", "Settlement Amount", "Risk Score", "Settlement %"]
    },
    generic: {
      aliases: [],
      commonMetrics: []
    }
  };
  var ANALYTICS_VALIDATOR_REGISTRY = [];

  function manualPromptIsStatusText(question) {
    return /\b(required\s+columns?\s+cannot|cannot\s+be\s+confidently\s+resolved|manual\s+analytics\s+needs|invalid\s+json|validation\s+issues?|no\s+unchecked\s+result|clearer\s+metric|clearer\s+grouping)\b/i.test(String(question || ""));
  }

  function manualPromptNeedsDatasetGuidance(question) {
    var q = String(question || "");
    return /\b(interpret|interpretation|explain\s+(?:this\s+)?(?:sheet|dataset|csv|data)|what\s+can\s+you\s+(?:tell|say)|what\s+does\s+this\s+(?:sheet|dataset|csv|data)\s+(?:show|contain|mean)|summari[sz]e\s+(?:this\s+)?(?:sheet|dataset|csv|data)|help\s+me\s+understand|available\s+(?:questions|analysis|insights)|what\s+(?:questions|analysis|insights)\s+can\s+i\s+ask)\b/i.test(q) &&
      !/\b(total|sum|average|avg|mean|median|min|max|count|rate|percentage|percent|correlation|iqr|verify|mismatch|top|bottom|highest|lowest|by\s+[A-Za-z])\b/i.test(q);
  }

  function manualNarrativeInterpretationIntent(question) {
    var q = String(question || "");
    if (!q.trim()) return false;
    if (manualPromptIsStatusText(q)) return true;
    return /\b(interpret|interpretation|explain|insights?|summary|summari[sz]e|narrative|business|clinical|operational|recommendations?|risks?|opportunities|what\s+can\s+you\s+(?:tell|say)|what\s+does\s+this\s+(?:sheet|dataset|csv|data)\s+(?:show|contain|mean)|help\s+me\s+understand|available\s+(?:questions|analysis|insights)|what\s+(?:questions|analysis|insights)\s+can\s+i\s+ask)\b/i.test(q) &&
      !/\b(total|sum|average|avg|mean|median|min|max|count|rate|percentage|percent|correlation|iqr|verify|mismatch|top|bottom|highest|lowest|show\s+.*\s+by\s+)\b/i.test(q);
  }

  function manualNarrativePrompt(metadata, question) {
    metadata = metadata || {};
    var profile = analyticsSemanticProfile(metadata);
    var metrics = (profile.metrics || []).map(function (item) { return item.name; }).slice(0, 12);
    var dimensions = (profile.dimensions || []).map(function (item) { return item.name; }).slice(0, 12);
    var grounding = manualNarrativeGroundingContext(metadata);
    return [
      "Answer as AI Sheet Analyzer narrative interpretation.",
      "hybrid_rule: All AI narrative intents must receive deterministic tables/KPIs/statistics as grounded context. AI is prohibited from inventing or recomputing unsupported values.",
      "Use only the grounded_context JSON below. Do not invent columns, values, KPIs, trends, rankings, correlations, rates, or categories.",
      "Do not cite any exact number unless that number appears in grounded_context.",
      "Do not calculate a specific metric unless the user clearly requested one.",
      "Return concise Editor.js blocks as JSON if possible: paragraph and unordered list blocks only. Do not create chart or table blocks.",
      "If the prompt is vague or says columns cannot be resolved, explain what the sheet contains and ask for a specific metric/group/filter next.",
      "Detected domain: " + profile.domain + ".",
      "Rows: " + (metadata.rowCount || rowsForAnalytics(metadata).length || 0) + ". Columns: " + (metadata.columnCount || (metadata.headings || []).length || 0) + ".",
      "Available metrics: " + (metrics.length ? metrics.join(", ") : "none detected") + ".",
      "Available grouping fields: " + (dimensions.length ? dimensions.join(", ") : "none detected") + ".",
      "grounded_context: " + JSON.stringify(grounding),
      "User prompt: " + String(question || "")
    ].join("\n");
  }

  function manualNarrativeGroundingContext(metadata) {
    metadata = metadata || {};
    var profile = analyticsSemanticProfile(metadata);
    var missingValues = metadata.missingValues || {};
    var numericStats = metadata.numericStats || {};
    var categoryStats = metadata.categoryStats || metadata.categoryFrequencies || {};
    var headings = (metadata.headings || []).map(String);
    var missingTop = Object.keys(missingValues).map(function (column) {
      return { column: column, missing: Number(missingValues[column]) || 0 };
    }).filter(function (item) { return item.missing > 0; }).sort(function (a, b) { return b.missing - a.missing; }).slice(0, 12);
    var numericTop = Object.keys(numericStats).slice(0, 18).map(function (column) {
      var stats = numericStats[column] || {};
      return {
        column: column,
        count: Number(stats.count || stats.nonEmpty || 0) || null,
        min: normalizeGroundedNumber(stats.min),
        max: normalizeGroundedNumber(stats.max),
        sum: normalizeGroundedNumber(stats.sum),
        average: normalizeGroundedNumber(stats.average || stats.avg),
        median: normalizeGroundedNumber(stats.median)
      };
    });
    var categoryTop = Object.keys(categoryStats).slice(0, 12).map(function (column) {
      var stats = categoryStats[column] || {};
      var values = stats.top || stats.values || stats.frequencies || [];
      if (!Array.isArray(values) && values && typeof values === "object") {
        values = Object.keys(values).slice(0, 8).map(function (key) { return { value: key, count: values[key] }; });
      }
      return {
        column: column,
        values: (Array.isArray(values) ? values : []).slice(0, 8)
      };
    });
    return {
      hybrid_rule: "All AI narrative intents must receive deterministic tables/KPIs/statistics as grounded context. AI is prohibited from inventing or recomputing unsupported values.",
      domain: profile.domain || "generic",
      shape: {
        rows: Number(metadata.rowCount || rowsForAnalytics(metadata).length || 0) || 0,
        columns: Number(metadata.columnCount || headings.length || 0) || 0,
        duplicateRows: Number(metadata.duplicateRows || 0) || 0,
        emptyRows: Number(metadata.emptyRows || 0) || 0
      },
      columns: headings,
      metricColumns: (profile.metrics || []).map(function (item) { return item.name; }).slice(0, 24),
      groupingColumns: (profile.dimensions || []).map(function (item) { return item.name; }).slice(0, 24),
      dateColumns: (metadata.dateColumns || []).map(String).slice(0, 12),
      missingValuesTop: missingTop,
      numericStats: numericTop,
      categoryPreviews: categoryTop
    };
  }

  function narrativeFallbackAnswerBlocks(metadata, question, issues) {
    metadata = metadata || {};
    var profile = analyticsSemanticProfile(metadata);
    var rows = Number(metadata.rowCount || rowsForAnalytics(metadata).length || 0) || 0;
    var columns = Number(metadata.columnCount || (metadata.headings || []).length || 0) || 0;
    var metrics = (profile.metrics || []).map(function (item) { return item.name; }).slice(0, 10);
    var dimensions = (profile.dimensions || []).map(function (item) { return item.name; }).slice(0, 10);
    var missing = metadata.missingValues || {};
    var missingItems = Object.keys(missing).filter(function (column) {
      return Number(missing[column] || 0) > 0;
    }).sort(function (a, b) {
      return Number(missing[b]) - Number(missing[a]);
    }).slice(0, 5).map(function (column) {
      return columnLabel(column) + ": " + chartNumber(missing[column]) + " missing";
    });
    var examples = profile.domain === "healthcare"
      ? ["Show total hospital billing.", "Count encounters by Department.", "What is the ICU utilization rate?", "How many patients have more than one encounter?"]
      : suggestedQuestionItems(metadata).slice(0, 4);
    if (!examples.length) {
      examples = [
        "Show total " + columnLabel(metrics[0] || "the main metric") + ".",
        "Count rows by " + columnLabel(dimensions[0] || "a category") + ".",
        "Show top 10 " + columnLabel(dimensions[0] || "groups") + " by " + columnLabel(metrics[0] || "metric") + "."
      ];
    }
    var items = [
      { text: "Detected domain: " + (profile.domain || "generic") + ".", level: 0 },
      { text: "Sheet shape: " + chartNumber(rows) + " rows and " + chartNumber(columns) + " columns.", level: 0 },
      { text: "Available metrics include: " + (metrics.length ? metrics.map(columnLabel).join(", ") : "no numeric metrics detected") + ".", level: 0 },
      { text: "Available grouping fields include: " + (dimensions.length ? dimensions.map(columnLabel).join(", ") : "no grouping fields detected") + ".", level: 0 },
      { text: "Data quality notes: " + (missingItems.length ? missingItems.join("; ") : "no missing values were highlighted") + "; duplicate rows: " + chartNumber(Number(metadata.duplicateRows || 0)) + ".", level: 0 }
    ];
    if (Array.isArray(issues) && issues.length) {
      items.push({ text: "The AI narrative answer was replaced with this grounded fallback because validation found: " + issues.slice(0, 2).join(" "), level: 0 });
    }
    return [
      {
        type: "paragraph",
        data: {
          text: "I can interpret the uploaded sheet, but this prompt does not name a specific metric, grouping, filter, or calculation. Here is a grounded summary from the parsed workbook metadata."
        }
      },
      { type: "list", data: { style: "unordered", items: items } },
      {
        type: "paragraph",
        data: {
          text: "Try asking: " + examples.join(" | ")
        }
      }
    ];
  }

  function deterministicNarrativeSummaryBlocks(metadata, question) {
    var q = String(question || "");
    if (!manualPromptLooksDatasetRelated(metadata, q)) return [];
    if (!/\b(summari[sz]e|issues?|bad|problem|anomal|interpret|insights?|previous|same|failed\s+rows|only\s+failed|blank\s+header|duplicate\s+headers?|mixed\s+dates?|mixed\s+percent|percentage|percent\s+normal|empty\s+rows?|0\s+data\s+rows?|zero\s+rows?|only\s+\d+\s+(?:data\s+)?rows?|wide\s+schema|dirty\s+categor|mixed\s+case|typos?|pick\s+useful\s+metrics|scatter)\b/i.test(q)) return [];
    var rows = Number(metadata && metadata.rowCount || rowsForAnalytics(metadata).length || 0) || 0;
    var columns = Number(metadata && metadata.columnCount || ((metadata && metadata.headings) || []).length || 0) || 0;
    var missing = (metadata && metadata.missingValues) || {};
    var missingItems = Object.keys(missing).filter(function (column) {
      return Number(missing[column] || 0) > 0;
    }).sort(function (a, b) {
      return Number(missing[b]) - Number(missing[a]);
    }).slice(0, 8).map(function (column) {
      return columnLabel(column) + ": " + chartNumber(missing[column]) + " missing";
    });
    var profile = analyticsSemanticProfile(metadata || {});
    var metrics = (profile.metrics || []).map(function (item) { return item.name; }).slice(0, 8);
    var dimensions = (profile.dimensions || []).map(function (item) { return item.name; }).slice(0, 8);
    var items = [
      { text: "Grounded interpretation from parsed workbook metadata only; no unsupported values are invented.", level: 0 },
      { text: "Sheet shape: " + chartNumber(rows) + " rows and " + chartNumber(columns) + " columns.", level: 0 },
      { text: "Data-quality evidence: " + (missingItems.length ? missingItems.join("; ") : "no missing-value columns were highlighted") + "; duplicate rows: " + chartNumber(Number((metadata || {}).duplicateRows || 0)) + ".", level: 0 },
      { text: "Candidate metrics: " + (metrics.length ? metrics.map(columnLabel).join(", ") : "none detected") + ".", level: 0 },
      { text: "Candidate grouping fields: " + (dimensions.length ? dimensions.map(columnLabel).join(", ") : "none detected") + ".", level: 0 }
    ];
    if (/\b(previous|same|failed\s+rows|only\s+failed|compare\s+with\s+previous)\b/i.test(q)) {
      items.push({ text: "This is a conversational follow-up. Use it after a compatible prior result, then carry forward that metric/filter context instead of inventing a new one.", level: 0 });
    }
    if (/\bblank\s+header|duplicate\s+headers?|ambiguous|bad|performance\b/i.test(q)) {
      items.push({ text: "Clarification is required when the prompt does not name a stable metric, grouping, filter, or disambiguated column.", level: 0 });
    }
    return [
      { type: "paragraph", data: { text: "Grounded dataset summary and clarification for this manual analytics prompt." } },
      { type: "table", data: { withHeadings: true, rows: [["Evidence", "Value"], ["Rows", String(chartNumber(rows))], ["Columns", String(chartNumber(columns))], ["Duplicate rows", String(chartNumber(Number((metadata || {}).duplicateRows || 0)))], ["Missing-value columns", String(missingItems.length)], ["Rows checked", String(chartNumber(rows))], ["Formula", "Parsed workbook metadata and normalized values only"]] } },
      { type: "list", data: { style: "unordered", items: items } }
    ];
  }

  function normalizeGroundedNumber(value) {
    var number = Number(value);
    if (!Number.isFinite(number)) return null;
    return chartNumber(number);
  }

  function manualPromptLooksDatasetRelated(metadata, question) {
    metadata = metadata || {};
    var q = String(question || "");
    if (!q.trim()) return false;
    if (manualPromptIsStatusText(q)) return true;
    if (/\b(rows?|columns?|sheet|dataset|csv|duplicates?|missing|blank|null|empty|total|sum|average|avg|mean|median|min|max|count|rate|percentage|percent|share|by|group|grouped|top|bottom|highest|lowest|rank|ranking|correlation|iqr|verify|mismatch|before|after|unique|distinct|encounter|patient|department|billing|bill|cost|claim|insurance|icu|readmission|mortality|outcome|wait|stay|satisfaction|age|date|issues?|bad|problem|anomal|previous|failed|performance|analy[sz]e|file|mixed\s+case|typos?|scatter)\b/i.test(q)) return true;
    var qKey = columnKey(q);
    if ((metadata.headings || []).some(function (heading) { return qKey.indexOf(columnKey(heading)) >= 0; })) return true;
    var profile = analyticsSemanticProfile(metadata);
    var pack = DOMAIN_KNOWLEDGE_PACKS[profile.domain] || DOMAIN_KNOWLEDGE_PACKS.generic;
    return (pack.aliases || []).some(function (term) { return new RegExp("\\b" + escapeRegExp(term) + "\\b", "i").test(q); });
  }

  function manualClarificationBlocks(metadata, question) {
    metadata = metadata || {};
    var profile = analyticsSemanticProfile(metadata);
    var headings = (metadata.headings || []).slice(0, 12);
    var metrics = (analyticsSemanticProfile(metadata).metrics || []).map(function (item) { return item.name; }).slice(0, 8);
    var dimensions = (analyticsSemanticProfile(metadata).dimensions || []).map(function (item) { return item.name; }).slice(0, 8);
    var examples = [];
    if (profile.domain === "healthcare") {
      examples = ["Show total hospital billing.", "Count encounters by Department.", "What is the ICU utilization rate?", "How many patients have more than one encounter?"];
    } else {
      examples = ["Show total " + columnLabel(metrics[0] || "the main metric") + ".", "Count rows by " + columnLabel(dimensions[0] || "a category") + ".", "Show top 10 " + columnLabel(dimensions[0] || "groups") + " by " + columnLabel(metrics[0] || "metric") + "."];
    }
    return [
      {
        type: "paragraph",
        data: {
          text: "Your uploaded " + profile.domain + " sheet has " + (metadata.rowCount || rowsForAnalytics(metadata).length || 0) + " rows and " + (metadata.columnCount || (metadata.headings || []).length || 0) + " columns. I cannot calculate a specific result from this prompt because it does not name a target metric, grouping, filter, or requested output."
        }
      },
      {
        type: "table",
        data: {
          withHeadings: true,
          rows: [
            ["Available field type", "Examples"],
            ["Metrics", metrics.length ? metrics.join(", ") : "No numeric metrics detected"],
            ["Groups", dimensions.length ? dimensions.join(", ") : "No grouping columns detected"],
            ["Columns", headings.length ? headings.join(", ") : "No headings detected"]
          ]
        }
      },
      { type: "paragraph", data: { text: "Try asking: " + examples.join(" | ") } }
    ];
  }

  function registerAnalyticsValidator(rule) {
    if (!rule || !rule.id || typeof rule.validate !== "function") return;
    ANALYTICS_VALIDATOR_REGISTRY.push(rule);
  }

  function analyticsEngineVersion() {
    return "2026-08-shared-engine-2";
  }

  function analyticsError(code, message, detail) {
    return { code: code, message: message, detail: detail || {} };
  }

  function ambiguityMetricCandidates(metadata, question) {
    metadata = metadata || {};
    var q = String(question || "");
    if (mentionedNumericColumns(metadata, q).length) return [];
    var genericMetricAsked = /\b(amount|value|total|sum|average|avg|metric)\b/i.test(q) && !/\b(bill|billing|revenue|sales|profit|salary|cost|treatment|medication|lab|claim|payment|age|stay|wait|satisfaction|count)\b/i.test(q);
    if (!genericMetricAsked) return [];
    var terms = /\bamount\b/i.test(q) ? ["amount", "total", "value"] : ["amount", "total", "value", "sum", "avg", "average"];
    var candidates = Object.keys(metadata.numericStats || {}).filter(function (column) {
      var info = semanticColumnInfo(metadata, column);
      if (info.identifier || info.avoidDefault) return false;
      var key = columnKey(column);
      return terms.some(function (term) { return key.indexOf(term) >= 0; });
    });
    if (candidates.length <= 1) return [];
    var explicit = candidates.filter(function (column) {
      var key = columnKey(column);
      return q.toLowerCase().indexOf(String(column || "").toLowerCase()) >= 0 || q.toLowerCase().indexOf(key) >= 0;
    });
    return explicit.length === 1 ? [] : candidates.slice(0, 8);
  }

  function validateExecutionPlan(metadata, question, plan) {
    metadata = metadata || {};
    plan = plan || {};
    var errors = [];
    var headings = metadata.headings || [];
    var numeric = Object.keys(metadata.numericStats || {});
    var q = String(question || "");
    var intentType = String(plan.intentType || "").toLowerCase();
    if (/^(?:clarification|missing_column_guard)$/i.test(intentType)) {
      return { ok: true, errors: [], issues: [] };
    }
    var identifierCountIntent = /^(?:distinct_count|repeat_count|distinct_repeat_count)$/.test(intentType);
    function isSyntheticCountMetric(metric) {
      return /^(?:__row_count|order[_\s-]*count|row[_\s-]*count|encounter[_\s-]*count|record[_\s-]*count|count|counts|rows?)$/i.test(String(metric || "").trim());
    }
    (plan.metrics || []).forEach(function (metric) {
      if (!metric || isSyntheticCountMetric(metric)) return;
      if (headings.indexOf(metric) < 0 && numeric.indexOf(metric) < 0 && !/%|rate|ratio|formula|revenue|profit|order count/i.test(String(metric))) {
        errors.push(analyticsError("COLUMN_NOT_FOUND", "Requested metric `" + metric + "` was not found in the dataset.", { metric: metric }));
      }
      if (semanticColumnInfo(metadata, metric).identifier && !identifierCountIntent && !/\b(unique|distinct|count|how\s+many|more\s+than\s+once|more\s+than\s+one|appear\s+more\s+than\s+once|repeat(?:ed|s)?)\b/i.test(q)) {
        errors.push(analyticsError("INVALID_AGGREGATION", "Identifier column `" + metric + "` cannot be used as a default numeric metric.", { metric: metric }));
      }
    });
    var normalizedDimensions = normalizePlanDimensions((plan.dimensions || []).concat(splitGroupByColumns(plan.groupBy || ""))).filter(function (dimension) {
      return !isForbiddenColumn(metadata, q, dimension);
    });
    normalizedDimensions.forEach(function (dimension) {
      if (!dimension || /^(time|month|date|year|quarter|week|period)$/i.test(String(dimension))) return;
      if (intentType === "scalar_rate" && /^(day|days)$/i.test(String(dimension)) && /\b\d+\s*[- ]?\s*day\b/i.test(q)) return;
      if (headings.indexOf(dimension) < 0) errors.push(analyticsError("COLUMN_NOT_FOUND", "Requested grouping column `" + dimension + "` was not found in the dataset.", { dimension: dimension }));
      if (semanticColumnInfo(metadata, dimension).identifier && !plan.rowLevelAllowed) {
        errors.push(analyticsError("INVALID_DIMENSION", "Identifier column `" + dimension + "` cannot be used as an automatic grouping dimension.", { dimension: dimension }));
      }
    });
    var ambiguousMetrics = ambiguityMetricCandidates(metadata, q);
    if (ambiguousMetrics.length) {
      errors.push(analyticsError("AMBIGUOUS_COLUMN", "The requested metric is ambiguous. Please choose one metric: " + ambiguousMetrics.join(", ") + ".", { candidates: ambiguousMetrics }));
    }
    if (/^grouped_missing_values$/i.test(String(plan.intentType || "")) && !(plan.dimensions || []).length) {
      errors.push(analyticsError("AMBIGUOUS_COLUMN", "The requested grouping column for missing values was not found in this sheet.", {}));
    }
    if (executionRequestsGrouping(q) && !/^(metadata_summary|metadata_mixed|sheet_shape|missing_values|grouped_missing_values|duplicate_count|duplicate_detail|clarification|missing_column_guard|time_series|relationship_scatter)$/i.test(String(plan.intentType || "")) && !normalizedDimensions.filter(function (dimension) { return !/^(time|month|date|year|quarter|week|period)$/i.test(String(dimension)); }).length && !/pivot|matrix|cross[-\s]*tab/i.test(q)) {
      errors.push(analyticsError("AMBIGUOUS_COLUMN", "The prompt asks for grouped analytics, but the grouping column could not be resolved confidently.", {}));
    }
    return {
      ok: !errors.length,
      errors: errors,
      issues: errors.map(function (error) { return error.message; })
    };
  }

  function makeExecutionResult(metadata, question, plan, blocks, validation, planValidation) {
    blocks = Array.isArray(blocks) ? blocks : [];
    validation = validation || { ok: true, issues: [] };
    planValidation = planValidation || { ok: true, errors: [], issues: [] };
    var snapshot = extractResultSnapshot(blocks);
    return {
      version: analyticsEngineVersion(),
      prompt: String(question || ""),
      intent_contract: plan && plan.contract || manualIntentContract(metadata || {}, question || ""),
      execution_plan: plan || buildAnalyticsExecutionPlan(metadata || {}, question || ""),
      blocks: blocks,
      tables: nestedTables(blocks),
      charts: nestedCharts(blocks),
      snapshot: snapshot,
      validation: {
        ok: !!(planValidation.ok && validation.ok),
        issues: (planValidation.issues || []).concat(validation.issues || []),
        plan: planValidation,
        result: validation,
        rulesRun: validation.rulesRun || [],
        blockingRules: validation.blockingRules || []
      },
      metadata: {
        source_row_count: Number((metadata && metadata.rowCount) || rowsForAnalytics(metadata || {}).length || 0),
        filtered_row_count: Number((metadata && metadata.rowCount) || rowsForAnalytics(metadata || {}).length || 0),
        domain: plan && plan.domain || (analyticsSemanticProfile(metadata || {}).domain)
      }
    };
  }

  function executionFailureBlocks(question, issues, count) {
    return validationFailureBlocks(question, issues || ["The analytics execution plan could not be validated."], count || 1);
  }

  function identifierColumns(metadata) {
    var headings = (metadata && metadata.headings) || [];
    return headings.filter(function (heading) {
      var key = columnKey(heading);
      return /\b(id|uuid|guid|code|number|no)$/i.test(String(heading || "")) || /(^|_)?id$/.test(key) || /(orderid|invoiceid|transactionid|customerid|productid|itemid|sku|employeeid|phonenumber|phone_no|phoneno|mobilenumber|mobile_no|mobileno|msisdn|simnumber|sim_no|simno|subscriberid|subscriptionid|accountnumber|account_no|accountno|imei|imsi)/.test(key);
    });
  }

  function mentionedIdentifierGroupingColumn(metadata, question) {
    var q = String(question || "");
    var qKey = columnKey(q);
    var mentioned = identifierColumns(metadata || {}).filter(function (column) {
      var key = columnKey(column);
      return key && (qKey.indexOf(key) >= 0 || q.toLowerCase().indexOf(String(column || "").toLowerCase()) >= 0);
    })[0] || "";
    if (mentioned) return mentioned;
    var knownIdentifier = [
      ["phonenumber", "PhoneNumber"],
      ["phone_no", "PhoneNumber"],
      ["phoneno", "PhoneNumber"],
      ["mobilenumber", "MobileNumber"],
      ["mobile_no", "MobileNumber"],
      ["mobileno", "MobileNumber"],
      ["msisdn", "MSISDN"],
      ["simnumber", "SIMNumber"],
      ["sim_no", "SIMNumber"],
      ["simno", "SIMNumber"],
      ["subscriberid", "SubscriberID"],
      ["subscriptionid", "SubscriptionID"],
      ["accountnumber", "AccountNumber"],
      ["account_no", "AccountNumber"],
      ["accountno", "AccountNumber"],
      ["imei", "IMEI"],
      ["imsi", "IMSI"]
    ].filter(function (item) { return qKey.indexOf(item[0]) >= 0; })[0];
    return knownIdentifier ? knownIdentifier[1] : "";
  }

  function highCardinalityIdentifierGroupingBlocks(metadata, question) {
    var q = String(question || "");
    if (!executionRequestsGrouping(q)) return [];
    if (/\b(top|bottom|rank|ranking|highest|lowest|best|worst)\b/i.test(q)) return [];
    var identifier = mentionedIdentifierGroupingColumn(metadata || {}, q);
    if (!identifier) return [];
    var numericStats = (metadata && metadata.numericStats) || {};
    var suggestions = ((metadata && metadata.headings) || []).filter(function (column) {
      return column !== identifier && !numericStats[column] && identifierColumns(metadata || {}).indexOf(column) < 0;
    }).slice(0, 5).map(columnLabel);
    return [
      { type: "paragraph", data: { text: columnLabel(identifier) + " is a high-cardinality identifier. I avoided grouping by it as a default because that would produce row-level output instead of useful analytics." } },
      { type: "table", data: { withHeadings: true, rows: [["Guard", "Recommendation"], ["Identifier grouping blocked", suggestions.length ? "Use a lower-cardinality category such as " + suggestions.join(", ") + "." : "Use a lower-cardinality categorical column."], ["Explicit identifier analysis", "Ask for top/bottom " + columnLabel(identifier) + " by a specific metric if that row-level ranking is intended."]] } }
    ];
  }

  function orderIdentifierColumn(metadata) {
    var aliases = columnAliases((metadata && metadata.headings) || []);
    return aliases.orderId || identifierColumns(metadata)[0] || "";
  }

  function missingValuePolicy(metadata, plan) {
    metadata = metadata || {};
    plan = plan || {};
    var columns = [].concat(plan.dimensions || [], plan.metrics || []).filter(Boolean).filter(function (column, index, list) {
      return list.indexOf(column) === index && column !== "__month__";
    });
    var missing = {};
    (metadata.columns || []).forEach(function (column) {
      if (columns.indexOf(column.name) >= 0 && Number(column.missing || 0) > 0) missing[column.name] = Number(column.missing || 0);
    });
    return {
      nullDimensions: "Blank values are grouped as Blank.",
      nullMetrics: "Blank numeric values are treated as 0 only after numeric parsing fails.",
      missingByColumn: missing
    };
  }

  function semanticColumnDefinitions() {
    return {
      order: ["orderid", "order_id", "order", "invoiceid", "invoice", "transactionid"],
      date: ["date", "sale date", "sales date", "orderdate", "createdat", "created"],
      customer: ["customer", "customername", "client", "buyer", "account"],
      customer_type: ["customer type", "customer_type", "client type", "buyer type", "customer segment", "segment", "channel"],
      category: ["category", "product category", "department", "segment", "type", "class"],
      product: ["drug name", "drug", "medicine", "product", "productname", "item", "sku", "itemname"],
      manufacturer: ["manufacturer", "maker", "brand", "company", "supplier", "vendor"],
      region: ["region", "territory", "market", "country", "state", "city", "location"],
      city: ["city", "town"],
      payment: ["paymentmethod", "payment method", "payment", "paymenttype", "method"],
      revenue: ["revenue", "sales", "amount", "total", "total revenue", "ordertotal", "sales amount"],
      profit: ["profit", "net profit", "gross profit", "margin amount"],
      discount: ["discount", "discount amount", "discount value", "discount rate"],
      quantity: ["quantity", "qty", "units", "units sold", "total quantity", "total units"],
      unit_price: ["unitprice", "unit price", "price", "selling price", "rate"]
    };
  }

  function semanticColumnCandidates(metadata, semanticKey) {
    metadata = metadata || {};
    var headings = metadata.headings || [];
    var defs = semanticColumnDefinitions();
    var words = defs[semanticKey] || [];
    return headings.map(function (heading) {
      var key = columnKey(heading);
      var score = 0;
      words.forEach(function (word) {
        var alias = columnKey(word);
        if (key === alias) score = Math.max(score, 100);
        else if (key.indexOf(alias) >= 0 || alias.indexOf(key) >= 0) score = Math.max(score, 85);
        else if (alias && key.split(alias).length > 1) score = Math.max(score, 70);
      });
      return { column: heading, score: score };
    }).filter(function (item) { return item.score > 0; }).sort(function (a, b) { return b.score - a.score; });
  }

  function semanticColumn(metadata, semanticKey) {
    var match = semanticColumnCandidates(metadata, semanticKey)[0];
    return match && match.score >= 70 ? match.column : "";
  }

  function detectAnalyticsDomain(metadata) {
    var headingsText = ((metadata && metadata.headings) || []).join(" ").toLowerCase();
    var best = { name: "generic", score: 0 };
    Object.keys(DOMAIN_KNOWLEDGE_PACKS).forEach(function (name) {
      if (name === "generic") return;
      var score = DOMAIN_KNOWLEDGE_PACKS[name].aliases.reduce(function (total, alias) {
        return total + (headingsText.indexOf(alias.toLowerCase()) >= 0 ? 1 : 0);
      }, 0);
      if (score > best.score) best = { name: name, score: score };
    });
    return best.name;
  }

  function analyticsSchemaProfile(metadata) {
    metadata = metadata || {};
    var semantics = {};
    Object.keys(semanticColumnDefinitions()).forEach(function (key) {
      var candidates = semanticColumnCandidates(metadata, key);
      semantics[key] = {
        column: candidates[0] ? candidates[0].column : "",
        confidence: candidates[0] ? candidates[0].score : 0,
        candidates: candidates.slice(0, 3)
      };
    });
    return {
      domain: detectAnalyticsDomain(metadata),
      rowCount: Number(metadata.rowCount || (rowsForAnalytics(metadata) || []).length || 0),
      headings: metadata.headings || [],
      numericColumns: Object.keys(metadata.numericStats || {}),
      dateColumns: metadata.dateColumns || [],
      semantics: semantics,
      formulas: SHEET_FORMULA_REGISTRY
    };
  }

  function deterministicRouteCapabilities(metadata) {
    var profile = analyticsSchemaProfile(metadata);
    var has = function (key) { return !!(profile.semantics[key] && profile.semantics[key].column); };
    return {
      groupedMetric: profile.numericColumns.length > 0 && profile.headings.length > 1,
      pivotMatrix: profile.headings.length > 2 && profile.numericColumns.length > 0,
      timeSeries: has("date") && profile.numericColumns.length > 0,
      multiSeriesTime: has("date") && profile.numericColumns.length > 0 && profile.headings.length > 2,
      formulaRatios: has("revenue") && (has("profit") || has("discount") || has("quantity")),
      kpiDashboard: has("revenue") || has("profit") || has("quantity"),
      rowOutliers: profile.numericColumns.length > 1,
      bucketComparison: has("date") && profile.headings.length > 2,
      contribution: has("revenue") || has("profit") || has("quantity")
    };
  }

  function formulaRequiredColumnsAvailable(profile, formulaKey) {
    var formula = SHEET_FORMULA_REGISTRY[formulaKey] || {};
    return (formula.required || []).filter(function (semanticKey) {
      return !(profile.semantics[semanticKey] && profile.semantics[semanticKey].column);
    });
  }

  function analyticsIntentClarifications(metadata, question, plan) {
    var q = String(question || "");
    var profile = analyticsSchemaProfile(metadata);
    var notes = [];
    if (/\bcustomer\s+segment\b/i.test(q) && !(profile.semantics.customer_type && profile.semantics.customer_type.column)) notes.push("No customer segment/type column was found; use monthly totals or choose an available categorical column.");
    if (/\bper\s+unit|revenue\s*\/\s*(unit|units|quantity)|average\s+revenue\s+per\s+unit\b/i.test(q) && formulaRequiredColumnsAvailable(profile, "revenue_per_unit").length) notes.push("Average revenue per unit needs both revenue and units/quantity columns.");
    if (/\bprofit\s+margin|margin\s*%/i.test(q) && formulaRequiredColumnsAvailable(profile, "profit_margin_pct").length) notes.push("Profit margin needs both revenue and profit columns.");
    if (/\bmatrix|pivot|rows\s+are|columns\s+are\b/i.test(q) && !(plan && plan.intentType === "pivot_matrix")) notes.push("Matrix/pivot prompts must preserve row dimension, column dimension, and value metric.");
    if (/\bwaterfall\b/i.test(q) && (!profile.semantics.revenue.column || (!profile.semantics.discount.column && !profile.semantics.profit.column))) notes.push("Waterfall analysis needs a start metric and at least one bridge/end metric.");
    return notes;
  }

  function analyticsConfidenceScore(metadata, question, plan) {
    var profile = analyticsSchemaProfile(metadata);
    var score = 100;
    var reasons = [];
    (plan.formulas || []).forEach(function (key) {
      var missing = formulaRequiredColumnsAvailable(profile, key);
      if (missing.length) {
        score -= 25;
        reasons.push("Missing formula columns for " + (SHEET_FORMULA_REGISTRY[key] || {}).label + ": " + missing.join(", "));
      }
    });
    if (/\b(month|quarter|year|trend|over time)\b/i.test(question || "") && !(profile.semantics.date && profile.semantics.date.column)) {
      score -= 25;
      reasons.push("No date column mapped for time-series intent.");
    }
    if (/\bmatrix|pivot|rows\s+are|columns\s+are\b/i.test(question || "") && !(plan && plan.intentType === "pivot_matrix")) {
      score -= 30;
      reasons.push("Prompt asks for matrix/pivot, but no pivot execution contract was created.");
    }
    if (/\btop|bottom|rank|outlier|unusually\b/i.test(question || "") && !((plan && plan.limit) || /\boutlier/i.test(plan && plan.intentType || ""))) {
      score -= 10;
      reasons.push("Ranking/outlier prompt should produce a bounded ranked table.");
    }
    return {
      score: Math.max(0, Math.min(100, score)),
      reasons: reasons,
      domain: profile.domain
    };
  }

  function buildAdaptiveAnalyticsLayer(metadata, question, plan) {
    var profile = analyticsSchemaProfile(metadata);
    return {
      version: "2026-08-adaptive-analytics-1",
      domain: profile.domain,
      schemaProfile: profile,
      routeCapabilities: deterministicRouteCapabilities(metadata),
      confidence: analyticsConfidenceScore(metadata, question, plan || {}),
      clarifications: analyticsIntentClarifications(metadata, question, plan || {})
    };
  }

  function dateGranularityFromPrompt(question) {
    var q = String(question || "");
    if (/\b(day|daily|date-wise|datewise|individual\s+date)\b/i.test(q)) return "day";
    if (/\b(year|yearly|annual)\b/i.test(q)) return "year";
    if (/\b(week|weekly)\b/i.test(q)) return "week";
    if (/\b(month|monthly|month-wise|monthwise)\b/i.test(q)) return "month";
    return "";
  }

  function normalizeDateBucket(value, granularity) {
    var date = new Date(value);
    if (Number.isNaN(date.getTime())) return null;
    if (granularity === "year") return { key: date.getFullYear(), label: String(date.getFullYear()) };
    if (granularity === "week") {
      var first = new Date(date.getFullYear(), 0, 1);
      var dayMs = 86400000;
      var week = Math.ceil((((date - first) / dayMs) + first.getDay() + 1) / 7);
      return { key: date.getFullYear() * 100 + week, label: date.getFullYear() + " W" + String(week).padStart(2, "0") };
    }
    if (granularity === "day") return { key: date.getFullYear() * 10000 + (date.getMonth() + 1) * 100 + date.getDate(), label: date.toISOString().slice(0, 10) };
    return monthBucket(value);
  }

  function formulaKeysForQuestion(metadata, question) {
    var q = String(question || "");
    var keys = [];
    if (/\bprofit\s+share|share\s+by\s+total\s+profit|profit\s+contribution\b/i.test(q)) keys.push("profit_share_pct");
    if (/\brevenue\s+share|share\s+of\s+revenue|revenue\s+contribution\b/i.test(q)) keys.push("revenue_share_pct");
    if (/\b(unit|units|quantity)\s+share|share\s+of\s+(unit|units|quantity)|units?\s+contribution\b/i.test(q)) keys.push("units_share_pct");
    if (/\bprofit\s+margin|margin\s*%|\bmargin\b/i.test(q)) keys.push("profit_margin_pct");
    if (/\bdiscount\s*%|discount\s+percentage\b/i.test(q)) keys.push("discount_pct");
    if (/\bnet\s+revenue\b/i.test(q)) keys.push("net_revenue");
    if (/\baverage\s+revenue\s+per\s+unit|revenue\s*\/\s*(unit|units|quantity|units sold)|per\s+unit\b/i.test(q)) keys.push("revenue_per_unit");
    if (/\baov\b|average\s+order\s+value\b/i.test(q)) keys.push("aov");
    if (/\b(previous|compared\s+with|change|increased|decreased|growth|mom|qoq|quarter-over-quarter|month-over-month)\b/i.test(q)) keys.push("period_change");
    if (/\bratio|rate|percentage|percent|%|\s\/\s/i.test(q)) keys.push("custom_ratio");
    (mentionedNumericColumns(metadata, q) || []).forEach(function (metric) {
      var key = columnKey(metric);
      if (key.indexOf("revenue") >= 0 && keys.indexOf("revenue_sum") < 0) keys.push("revenue_sum");
      if (key.indexOf("profit") >= 0 && keys.indexOf("profit_sum") < 0) keys.push("profit_sum");
      if (key.indexOf("discount") >= 0 && keys.indexOf("discount_sum") < 0) keys.push("discount_sum");
      if (/(quantity|qty|units|unitssold)/.test(key) && keys.indexOf("units_share_pct") < 0 && /\bshare|contribution|percent|%/.test(q)) keys.push("units_share_pct");
    });
    if (/\border\s+count|count\s+orders|number\s+of\s+orders|how\s+many\s+orders\b/i.test(q)) keys.push("order_count");
    return keys.filter(function (key, index, list) { return list.indexOf(key) === index; });
  }

  function buildAnalyticsExecutionPlan(metadata, question) {
    metadata = metadata || {};
    var contract = manualIntentContract(metadata, question);
    var q = String(question || "");
    var plan = {
      version: analyticsEngineVersion(),
      intentType: contract.intentType,
      deterministicRoute: contract.deterministicRoute || "",
      chartType: contract.chartType || (asksForGraph(q) ? "auto" : ""),
      dimensions: [],
      metrics: [],
      formulas: formulaKeysForQuestion(metadata, q),
      aggregation: contract.aggregation || "",
      filters: [],
      sort: {},
      limit: requestedLimit(q, 0) || null,
      dateGranularity: dateGranularityFromPrompt(q),
      rowLevelAllowed: contract.rowLevelAllowed !== false,
      forbiddenColumns: contract.forbiddenColumns || [],
      identifiers: identifierColumns(metadata),
      contract: contract
    };
    if (contract.groupBy) splitGroupByColumns(contract.groupBy).forEach(function (dimension) {
      if (plan.dimensions.indexOf(dimension) < 0) plan.dimensions.push(dimension);
    });
    if (contract.timeGroupBy || plan.dateGranularity) plan.dimensions.unshift(plan.dateGranularity || "time");
    if (contract.xMetric) plan.metrics.push(contract.xMetric);
    if (contract.yMetric) plan.metrics.push(contract.yMetric);
    if (contract.metric) String(contract.metric).split(",").forEach(function (metric) {
      metric = String(metric || "").trim();
      if (metric === "__row_count") return;
      if (metric && plan.metrics.indexOf(metric) < 0) plan.metrics.push(metric);
    });
    if (contract.pivot) {
      plan.intentType = "pivot_matrix";
      plan.dimensions = (contract.pivot.rowColumn === "__month__" ? ["Month"] : ((Array.isArray(contract.pivot.rowColumns) && contract.pivot.rowColumns.length) ? contract.pivot.rowColumns.slice() : [contract.pivot.rowColumn])).concat([contract.pivot.columnColumn]).filter(function (dimension, index, list) {
        return dimension && list.indexOf(dimension) === index;
      });
      plan.metrics = contract.pivot.valueMode === "count" ? [] : [contract.pivot.valueColumn];
      plan.aggregation = contract.pivot.valueMode === "count" ? "count" : "sum";
      plan.chartType = contract.requiresChart ? plan.chartType : "";
    }
    if (contract.combination) {
      plan.dimensions = contract.combination.columns.slice();
      var combinationMeasures = requestedNumericMeasures(metadata, q).map(function (measure) { return measure.column; }).filter(Boolean);
      plan.metrics = ["Order Count"].concat(combinationMeasures.length ? combinationMeasures : [contract.combination.sortMetric]).filter(function (metric, index, list) {
        return metric && list.indexOf(metric) === index;
      });
    }
    plan.missingPolicy = missingValuePolicy(metadata, plan);
    plan.adaptive = buildAdaptiveAnalyticsLayer(metadata, q, plan);
    plan.domain = plan.adaptive.domain;
    plan.schemaProfile = plan.adaptive.schemaProfile;
    plan.routeCapabilities = plan.adaptive.routeCapabilities;
    plan.confidence = plan.adaptive.confidence;
    plan.clarifications = plan.adaptive.clarifications;
    return plan;
  }

  function routeForStoredIntentContract(contract) {
    var intent = String(contract && contract.intentType || "").toLowerCase();
    var aggregation = String(contract && contract.aggregation || "").toLowerCase();
    var prompt = String(contract && (contract.prompt || contract.promptText) || "").toLowerCase();
    if (intent === "metadata_mixed" || /shape_and_missing/.test(aggregation)) return "metadataMixedBlocks";
    if (intent === "metadata_summary" || intent === "sheet_shape" || intent === "missing_values" || intent === "duplicate_count") return "scalarManualCheckBlocks";
    if (intent === "duplicate_detail") return "duplicateDetailBlocks";
    if (intent === "grouped_missing_values") return "groupedMissingValuesBlocks";
    if (intent === "distinct_count" || intent === "repeat_count" || intent === "distinct_repeat_count") return "distinctAndRepeatBlocks";
    if (intent === "correlation") return "correlationBlocks";
    if (intent === "relationship_scatter") return "manualRelationshipBlocks";
    if (intent === "iqr_outlier_count") return "iqrOutlierBlocks";
    if (intent === "distribution_stats") return "distributionStatsBlocks";
    if (intent === "threshold_count") return "thresholdCountBlocksV2";
    if (intent === "missing_column_guard") return "missingColumnGuardBlocks";
    if (intent === "formula_validation") return "formulaValidationBlocks";
    if (intent === "pivot_matrix" || intent === "cross_tab" || intent === "cross_tab_count") return "pivotMatrixBlocks";
    if (intent === "time_series" || intent === "time_trend") return "timeSeries";
    if (intent === "time_ranking") return "monthHighLowMetricBlocks";
    if (intent === "ranking" || intent === "rank_sort" || intent === "combination_ranking") return "ranking";
    if (intent === "grouped_count") return "groupedCountBlocks";
    if (intent === "vs_comparison" && /\b(rate|percentage|percent|%)\b/.test(prompt + " " + aggregation) && (/\bvs\b|\bversus\b|\bnon[-\s]?\w+/.test(prompt))) return "binaryRateComparisonBlocks";
    if (intent === "vs_comparison" && /\b(avg|average|mean|total|sum)\b/.test(prompt + " " + aggregation) && (/\bvs\b|\bversus\b|\bnon[-\s]?\w+/.test(prompt))) return "averageByFlagComparisonBlocks";
    if (intent === "grouped_rate") return "groupedRateBlocks";
    if (intent === "grouped_share") return "singleContributionShareBlocks";
    if (intent === "filtered_rate" || intent === "vs_comparison") return "genericMultiMetricGroupBlocks";
    if (intent === "filtered_rows") return "filteredRowBlocks";
    if (intent === "filtered_aggregation") return "filteredAggregationBlocks";
    if (intent === "threshold_count") return "thresholdCountBlocksV2";
    if (intent === "clarification") return "clarificationBlocks";
    if (intent === "natural_ranking") return "ranking";
    if (intent === "grouped_analytics" || intent === "grouped_metric" || intent === "distribution_share") return "groupedMetricBlocks";
    if (intent === "date_logic") return "scalarManualCheckBlocks";
    if (intent === "scalar_rate") return "scalarMetricExecutionBlocks";
    if (intent === "scalar_metric" || intent === "scalar_count") return "scalarMetricExecutionBlocks";
    return contract && contract.deterministicRoute || "";
  }

  function normalizeStoredIntentContract(metadata, question, rawContract) {
    if (!rawContract) return null;
    var stored = rawContract.contract && typeof rawContract.contract === "object" ? rawContract.contract : rawContract;
    var base = manualIntentContract(metadata, question);
    var metric = String(stored.metric || rawContract.metric || base.metric || "").trim();
    var metrics = metric.split(",").map(function (item) { return item.trim(); }).filter(Boolean);
    var intentType = String(stored.intentType || rawContract.intentType || base.intentType || "general");
    var contract = Object.assign({}, base, stored, {
      intentType: intentType,
      metric: metric || base.metric || "",
      groupBy: String(stored.groupBy || rawContract.groupBy || base.groupBy || "").trim(),
      aggregation: String(stored.aggregation || rawContract.aggregation || base.aggregation || "deterministic").trim(),
      requiresTable: stored.requiresTable != null ? !!stored.requiresTable : String(stored.outputType || rawContract.outputType || "").toLowerCase() !== "paragraph",
      requiresChart: !!stored.requiresChart,
      rowLevelAllowed: stored.rowLevelAllowed != null ? !!stored.rowLevelAllowed : !!base.rowLevelAllowed,
      deterministicRoute: String(stored.deterministicRoute || routeForStoredIntentContract(stored) || routeForStoredIntentContract(rawContract) || base.deterministicRoute || ""),
      source: rawContract.source || stored.source || "sheet_prompt_bank",
      prompt: String(stored.prompt || rawContract.prompt || question || "")
    });
    if (/^(?:scalar_rate|vs_comparison|grouped_rate|date_logic|correlation|iqr_outlier_count|threshold_count|formula_validation|duplicate_count|duplicate_detail|repeat_count|distinct_count)$/.test(String(base.intentType || "")) && base.deterministicRoute && base.intentType !== intentType) {
      contract.intentType = base.intentType;
      contract.metric = base.metric || "";
      contract.groupBy = base.groupBy || "";
      contract.aggregation = base.aggregation || "deterministic";
      contract.requiresTable = base.requiresTable;
      contract.requiresChart = base.requiresChart;
      contract.rowLevelAllowed = base.rowLevelAllowed;
      contract.deterministicRoute = base.deterministicRoute;
      contract.prompt = String(question || contract.prompt || "");
      contract.source = "live_prompt_override";
    }
    if (base.intentType === "scalar_metric" && base.metric && contract.intentType !== "scalar_metric") {
      contract.intentType = "scalar_metric";
      contract.metric = base.metric;
      contract.groupBy = "";
      contract.aggregation = base.aggregation || aggregationMode(question);
      contract.requiresTable = true;
      contract.requiresChart = false;
      contract.rowLevelAllowed = false;
      contract.deterministicRoute = "scalarMetricExecutionBlocks";
      contract.prompt = String(question || contract.prompt || "");
      contract.source = "live_prompt_override";
    }
    if (base.intentType === "scalar_metric" && base.metric && contract.intentType === "scalar_metric" && (/\b(min(?:imum)?|lowest|least|max(?:imum)?|highest|most|range)\b/i.test(question))) {
      contract.metric = base.metric;
      contract.groupBy = "";
      contract.aggregation = base.aggregation || aggregationMode(question);
      contract.requiresTable = true;
      contract.requiresChart = false;
      contract.rowLevelAllowed = false;
      contract.deterministicRoute = "scalarMetricExecutionBlocks";
      contract.prompt = String(question || contract.prompt || "");
      contract.source = "live_prompt_override";
    }
    if (intentType === "correlation" && metrics.length >= 2) {
      contract.xMetric = contract.xMetric || metrics[0];
      contract.yMetric = contract.yMetric || metrics[1];
    }
    if ((intentType === "distinct_count" || intentType === "repeat_count" || intentType === "distinct_repeat_count") && contract.metric) {
      contract.intentType = intentType === "distinct_repeat_count" ? (/unique|distinct/i.test(contract.prompt || question || "") ? "distinct_count" : "repeat_count") : intentType;
      contract.deterministicRoute = "distinctAndRepeatBlocks";
    }
    if (/^(cross_tab|cross_tab_count|pivot_matrix)$/.test(intentType)) {
      var groupParts = String(contract.groupBy || "").split(",").map(function (item) { return item.trim(); }).filter(Boolean);
      if (!contract.pivot && groupParts.length >= 2) {
        contract.pivot = {
          rowColumn: groupParts[0],
          rowColumns: [groupParts[0]],
          columnColumn: groupParts[1],
          valueColumn: contract.metric === "__row_count" || /count/i.test(contract.aggregation) ? "" : contract.metric,
          valueMode: contract.metric === "__row_count" || /count/i.test(contract.aggregation) ? "count" : "sum"
        };
      }
      contract.intentType = contract.pivot && contract.pivot.valueMode === "count" ? "cross_tab_count" : contract.intentType;
      contract.deterministicRoute = "pivotMatrixBlocks";
      contract.rowLevelAllowed = false;
      contract.requiresTable = true;
      contract.requiresChart = !!contract.requiresChart && asksForGraph(question);
      if (!contract.requiresChart) contract.chartType = "";
    }
    if (intentType === "grouped_count") {
      contract.metric = "__row_count";
      contract.rowLevelAllowed = false;
      contract.deterministicRoute = "groupedCountBlocks";
    }
    if (dateComparisonRowCountQuestion(question) || dateComparisonRowCountQuestion(contract.prompt)) {
      contract.intentType = "date_logic";
      contract.metric = "";
      contract.groupBy = "";
      contract.aggregation = "date_comparison_count";
      contract.rowLevelAllowed = false;
      contract.requiresTable = true;
      contract.requiresChart = false;
      contract.deterministicRoute = "scalarManualCheckBlocks";
    }
    return contract;
  }

  function manualContractQuestion(metadata, question, contract) {
    contract = contract || {};
    if (contract.source !== "sheet_prompt_bank" && contract.source !== "manual_success_contract" && !contract.deterministicRoute && !contract.intentType) return String(question || "");
    var intent = String(contract.intentType || "").toLowerCase();
    var metric = String(contract.metric || "").trim();
    var group = String(contract.groupBy || "").trim();
    var aggregation = String(contract.aggregation || "").toLowerCase();
    var limit = requestedLimit(question, 10) || 10;
    if (String(contract.deterministicRoute || "") === "genericMultiMetricGroupBlocks") return contract.prompt || String(question || "");
    if (intent === "metadata_mixed") return "How many rows and columns are in this sheet and count missing values column-wise.";
    if (intent === "missing_values") return "Count missing values column-wise.";
    if ((intent === "metadata_summary" || intent === "sheet_shape") && missingValuesIntent(question || contract.prompt || "")) return String(question || contract.prompt || "Count missing values column-wise.");
    if (intent === "metadata_summary" || intent === "sheet_shape") return "How many rows and columns are in this sheet?";
    if (intent === "duplicate_count") return "How many exact duplicate rows are present?";
    if (intent === "duplicate_detail") return "How many exact duplicate rows are present? Give row numbers or IDs.";
    if (intent === "grouped_missing_values" && group) return "Count missing values column-wise by " + group + ".";
    if (intent === "correlation" && contract.xMetric && contract.yMetric) return "Calculate correlation between " + contract.xMetric + " and " + contract.yMetric + ".";
    if (intent === "iqr_outlier_count" && metric) {
      if (/\babove\s+q3|greater\s+than\s+q3|high(?:er)?\s+outliers?\b/i.test(question || contract.prompt || "")) return "How many " + metric + " values are above Q3 + 1.5xIQR?";
      return "Using 1.5xIQR, count outliers in " + metric + ".";
    }
    if (intent === "distribution_stats" && metric) return "Give Q1, median, Q3 and IQR for " + metric + ".";
    if (intent === "formula_validation") return contract.prompt || String(question || "");
    if ((intent === "cross_tab" || intent === "cross_tab_count" || intent === "pivot_matrix") && contract.pivot) {
      var pivot = contract.pivot;
      var rowLabel = pivot.rowColumn === "__month__" ? "Month" : ((Array.isArray(pivot.rowColumns) && pivot.rowColumns.length) ? pivot.rowColumns.join(" and ") : pivot.rowColumn);
      var valueText = pivot.valueMode === "count" ? "row count" : ("sum " + (pivot.valueColumn || metric));
      return "Create a pivot table with rows as " + rowLabel + ", columns as " + pivot.columnColumn + ", and values as " + valueText + ".";
    }
    if (intent === "distinct_repeat_count" || intent === "distinct_count" || intent === "repeat_count") {
      if (/repeat|more than once|more than one/i.test(question || contract.prompt || "")) return "How many " + metric + " values appear more than once?";
      return "How many unique " + metric + " values are present?";
    }
    if ((intent === "ranking" || intent === "rank_sort" || intent === "combination_ranking") && metric && group) {
      var rankOp = /average|avg|mean/.test(aggregation) ? "average " : (/median/.test(aggregation) ? "median " : (/min/.test(aggregation) ? "minimum " : (/max/.test(aggregation) ? "maximum " : "total ")));
      if (rankingIsBottom(question || contract.prompt || "")) return "Show bottom " + limit + " " + group + " by " + rankOp + metric + ".";
      return "Show top " + limit + " " + group + " by " + rankOp + metric + ".";
    }
    if (intent === "date_logic") return contract.prompt || String(question || "");
    if (intent === "grouped_rate" || intent === "grouped_share" || intent === "filtered_rate" || intent === "vs_comparison" || intent === "filtered_aggregation" || intent === "filtered_rows" || intent === "threshold_count" || intent === "cross_tab_count" || intent === "natural_ranking" || intent === "clarification") return contract.prompt || String(question || "");
    if ((intent === "time_series" || intent === "time_trend") && metric) {
      var timeText = String(question || contract.prompt || "");
      if (/\byear[-\s]?wise|\byearly\b|\bannual\b|\bby\s+year\b/i.test(timeText) || columnKey(timeText).indexOf("yearwise") >= 0) return "Show yearly " + metric + ".";
      if (/\bquarter[-\s]?wise|quarterly|quarter\b/i.test(timeText)) return "Show quarter-wise total " + metric + ".";
      if (/\bmonth[-\s]?wise|\bmonthly\b|\bby\s+month\b|\bmonth\b/i.test(timeText)) return "Show month-wise total " + metric + ".";
      return "Show yearly " + metric + ".";
    }
    if (intent === "time_ranking" && metric) return "Which month had the highest and lowest " + metric + "?";
    if (intent === "grouped_count" && group) return "Count rows by " + group + ".";
    if (aggregation === "count" && group) return "Count rows by " + group + ".";
    if ((intent === "grouped_analytics" || intent === "grouped_metric" || intent === "grouped_share" || intent === "distribution_share") && metric && group) {
      var op = /average|avg/.test(aggregation) ? "average" : (/median/.test(aggregation) ? "median" : (/share|percent/.test(aggregation) ? "share %" : "total"));
      if (asksForTrend(question || contract.prompt || "")) return "Trend " + op + " " + metric + " by " + group + ".";
      return "Show " + op + " " + metric + " by " + group + ".";
    }
    if (intent === "scalar_rate") return contract.prompt || String(question || "");
    if ((intent === "scalar_metric" || intent === "scalar_count") && metric) {
      var scalarOp = /average|avg/.test(aggregation) ? "average" : (/median/.test(aggregation) ? "median" : (/min.*max|min_max|range/.test(aggregation) ? "minimum and maximum" : (/^min$|minimum/.test(aggregation) ? "minimum" : (/^max$|maximum/.test(aggregation) ? "maximum" : (/count/.test(aggregation) ? "count" : "total")))));
      return "Show " + scalarOp + " " + metric + ".";
    }
    return contract.prompt || String(question || "");
  }

  function buildAnalyticsExecutionPlanWithContract(metadata, question, providedContract) {
    var decision = providedContract ? canonicalManualAnalyticsDecision(metadata, question, providedContract) : null;
    var contract = decision ? decision.contract : null;
    var executionQuestion = contract ? manualContractQuestion(metadata, question, contract) : question;
    var plan = buildAnalyticsExecutionPlan(metadata, executionQuestion);
    if (!contract) return plan;
    plan.intentType = contract.intentType || plan.intentType;
    plan.deterministicRoute = contract.deterministicRoute || plan.deterministicRoute;
    plan.contract = contract;
    plan.dimensions = [];
    if (contract.groupBy) splitGroupByColumns(contract.groupBy).forEach(function (dimension) {
      if (plan.dimensions.indexOf(dimension) < 0) plan.dimensions.push(dimension);
    });
    if (contract.pivot) {
      var pivotRows = contract.pivot.rowColumn === "__month__" ? ["Month"] : ((Array.isArray(contract.pivot.rowColumns) && contract.pivot.rowColumns.length) ? contract.pivot.rowColumns.slice() : [contract.pivot.rowColumn]);
      plan.dimensions = pivotRows.concat([contract.pivot.columnColumn]).filter(function (dimension, index, list) {
        return dimension && list.indexOf(dimension) === index;
      });
    }
    plan.metrics = [];
    var identifierCountIntent = /^(?:distinct_count|repeat_count|distinct_repeat_count)$/.test(String(contract.intentType || "").toLowerCase());
    if (contract.xMetric) plan.metrics.push(contract.xMetric);
    if (contract.yMetric) plan.metrics.push(contract.yMetric);
    if (contract.metric) String(contract.metric).split(",").forEach(function (metric) {
      metric = String(metric || "").trim();
      if (identifierCountIntent) {
        plan.identifierMetric = metric;
        return;
      }
      if (metric === "__row_count") return;
      if (metric && plan.metrics.indexOf(metric) < 0) plan.metrics.push(metric);
    });
    plan.aggregation = contract.aggregation || plan.aggregation;
    plan.rowLevelAllowed = contract.rowLevelAllowed !== false;
    plan.requiresTable = contract.requiresTable;
    plan.requiresChart = contract.requiresChart;
    plan.contractQuestion = executionQuestion;
    plan.missingPolicy = missingValuePolicy(metadata, plan);
    return plan;
  }

  function executeManualIntentContract(metadata, question, contract) {
    contract = contract || manualIntentContract(metadata, question);
    var route = contract.deterministicRoute || "";
    var q = manualContractQuestion(metadata, question, contract);
    var blocks = [];
    function first(list) {
      for (var i = 0; i < list.length; i += 1) {
        var result = list[i]();
        if (result && result.length) return result;
      }
      return [];
    }
    if (route === "metadataMixedBlocks") blocks = metadataMixedBlocks(metadata, q);
    else if (route === "groupedMissingValuesBlocks") blocks = groupedMissingValuesBlocks(metadata, q);
    else if (route === "duplicateDetailBlocks") blocks = duplicateDetailBlocks(metadata, q);
    else if (route === "distinctAndRepeatBlocks") blocks = distinctAndRepeatBlocks(metadata, q);
    else if (route === "correlationBlocks") blocks = correlationBlocks(metadata, q);
    else if (route === "iqrOutlierBlocks") blocks = iqrOutlierBlocks(metadata, q);
    else if (route === "distributionStatsBlocks") blocks = distributionStatsBlocks(metadata, q);
    else if (route === "filteredRowBlocks") blocks = filteredRowBlocks(metadata, q);
    else if (route === "filteredAggregationBlocks") blocks = filteredAggregationBlocks(metadata, q);
    else if (route === "singleContributionShareBlocks") blocks = singleContributionShareBlocks(metadata, q);
    else if (route === "groupedRateBlocks") blocks = groupedRateBlocks(metadata, q);
    else if (route === "clarificationBlocks") blocks = clarificationBlocks(metadata, q);
    else if (route === "narrativeInterpretationBlocks" || contract.intentType === "narrative_interpretation") blocks = deterministicNarrativeSummaryBlocks(metadata, q);
    else if (route === "missingColumnGuardBlocks") blocks = missingColumnGuardBlocks(metadata, q);
    else if (route === "thresholdCountBlocks" || route === "thresholdCountBlocksV2") blocks = thresholdCountBlocksV2(metadata, q);
    else if (route === "claimStatusProviderBucketBlocks") blocks = claimStatusProviderBucketBlocks(metadata, q);
    else if (route === "formulaValidationBlocks") blocks = formulaValidationBlocks(metadata, q);
    else if (route === "scalarManualCheckBlocks") {
      blocks = first([
        function () { return formulaValidationBlocks(metadata, q); },
        function () { return scalarManualCheckBlocks(metadata, q); }
      ]);
    } else if (route === "scalarMetricExecutionBlocks") {
      blocks = first([
        function () { return scalarMetricExecutionBlocks(metadata, q); }
      ]);
    } else if (route === "pivotMatrixBlocks") blocks = pivotMatrixBlocks(metadata, q);
    else if (route === "combinationRankingBlocks") blocks = combinationRankingBlocks(metadata, q);
    else if (route === "formulaRatioBlocks") blocks = formulaRatioBlocks(metadata, q);
    else if (route === "monthOverMonthConditionBlocks") blocks = monthOverMonthConditionBlocks(metadata, q);
    else if (route === "groupedCountBlocks") blocks = groupedCountBlocks(metadata, q);
    else if (route === "exactCountBlocks") blocks = exactCountBlocks(metadata, q);
    else if (route === "binaryRateComparisonBlocks") blocks = binaryRateComparisonBlocks(metadata, q);
    else if (route === "averageByFlagComparisonBlocks") blocks = averageByFlagComparisonBlocks(metadata, q);
    else if (route === "genericMultiMetricGroupBlocks") blocks = first([
      function () { return binaryRateComparisonBlocks(metadata, q); },
      function () { return averageByFlagComparisonBlocks(metadata, q); },
      function () { return genericMultiMetricGroupBlocks(metadata, q, contract); }
    ]);
    else if (route === "manualDeterministicBlocks") blocks = manualDeterministicBlocks(metadata, q);
    else if (route === "monthlyDistributionBlocks") blocks = monthlyDistributionBlocks(metadata, q);
    else if (route === "manualRelationshipBlocks") blocks = manualRelationshipBlocks(metadata, q);
    else if (route === "monthHighLowMetricBlocks") blocks = monthHighLowMetricBlocks(metadata, q);
    else if (route === "sortedGroupBlocks") blocks = sortedGroupBlocks(metadata, q);
    else if (route === "timeTrendMetricBlocks" || route === "timeSeries") {
      blocks = first([
        function () { return timeTrendMetricBlocks(metadata, q); },
        function () { return genericTimeSeriesByGroupBlocks(metadata, q); },
        function () { return monthlyDistributionBlocks(metadata, q); }
      ]);
    } else if (route === "derivedMetricBlocks") blocks = derivedMetricBlocks(metadata, q);
    else if (route === "groupedMetricBlocks" || route === "grouped") {
      blocks = first([
        function () { return totalMetricByGroupBlocks(metadata, q); },
        function () { return groupedCountBlocks(metadata, q); },
        function () { return groupedMetricBlocks(metadata, q); },
        function () { return aggregateRankingBlocks(metadata, q); }
      ]);
    } else if (route === "ranking" || contract.intentType === "ranking") {
      blocks = first([
        function () { return aggregateRankingBlocks(metadata, q); },
        function () { return genericMultiMetricGroupBlocks(metadata, q, contract); },
        function () { return sortedGroupBlocks(metadata, q); }
      ]);
    }
    if (!blocks.length) blocks = first([
      function () { return (!route || contract.intentType === "general") ? manualDeterministicBlocks(metadata, q) : []; },
      function () { return deterministicNarrativeSummaryBlocks(metadata, q); }
    ]);
    return blocks || [];
  }

  function validateManualIntentContractResult(metadata, question, contract, blocks) {
    contract = contract || manualIntentContract(metadata, question);
    blocks = blocks || [];
    var issues = [];
    var text = flattenBlockText(blocks, { tableRows: 25, maxChars: 25000 });
    var tables = nestedTables(blocks);
    var charts = nestedCharts(blocks);
    function requireText(pattern, message) {
      if (!pattern.test(text)) issues.push(message);
    }
    if (!blocks.length) {
      issues.push("Manual analytics could not produce a deterministic answer for `" + (contract.intentType || "general") + "`.");
    } else if (contract.intentType === "correlation") {
      requireText(/pearson\s+correlation/i, "Correlation answer must include the Pearson correlation value.");
      requireText(/comparable\s+rows/i, "Correlation answer must include comparable row count.");
    } else if (contract.intentType === "metadata_mixed") {
      if (tables.length < 2) issues.push("Mixed metadata answer must include sheet shape and missing-value tables.");
      requireText(/rows|columns|missing/i, "Mixed metadata answer must include rows, columns, and missing values.");
    } else if (contract.intentType === "grouped_missing_values") {
      if (!tables.length) issues.push("Grouped missing-values answer must include a table.");
      requireText(/missing|group|rows/i, "Grouped missing-values answer must include group rows and missing-value counts.");
    } else if (contract.intentType === "duplicate_detail") {
      requireText(/row\s+numbers?/i, "Duplicate-detail answer must include duplicate row numbers.");
    } else if (contract.intentType === "distinct_repeat_count" || contract.intentType === "distinct_count" || contract.intentType === "repeat_count") {
      requireText(/distinct|unique|more\s+than\s+one|more\s+than\s+once|repeat|repeated/i, "Distinct/repeat answer must include the requested count.");
    } else if (contract.intentType === "iqr_outlier_count") {
      requireText(/iqr|threshold|rows\s+above/i, "IQR answer must include threshold/outlier count.");
    } else if (contract.intentType === "threshold_count") {
      requireText(/threshold|rows\s+matching|rows\s+checked|above|below|greater|less|negative/i, "Threshold-count answer must include matching rows and threshold condition.");
    } else if (contract.intentType === "formula_validation") {
      requireText(/mismatch|formula|rows\s+checked/i, "Formula validation answer must include formula, mismatch count, and rows checked.");
    } else if (contract.intentType === "filtered_aggregation") {
      if (!tables.length) issues.push("Filtered aggregation answer must include a table.");
      requireText(/filtered|where|matching\s+rows|rows\s+checked/i, "Filtered aggregation answer must include filters and matching rows.");
    } else if (contract.intentType === "grouped_rate") {
      if (!tables.length) issues.push("Grouped-rate answer must include a table.");
      requireText(/denominator|rate|matching\s+rows|rows\s+checked/i, "Grouped-rate answer must include per-group denominator logic.");
    } else if (contract.intentType === "grouped_share") {
      if (!tables.length) issues.push("Share answer must include a table.");
      requireText(/share|contribution|grand\s+total|total\s+sum|%/i, "Share answer must include group share/contribution formula.");
    } else if (contract.intentType === "distribution_stats") {
      if (!tables.length) issues.push("Distribution answer must include a table.");
      requireText(/q1|median|q3|iqr/i, "Distribution answer must include Q1, median, Q3, and IQR.");
    } else if (contract.intentType === "clarification") {
      requireText(/specific\s+metric|criterion|not\s+invent|contradictory|clarify|high-cardinality|identifier/i, "Clarification answer must ask for clarification or explain the guard.");
    } else if (contract.intentType === "missing_column_guard") {
      if (/contradiction/i.test(String(contract.aggregation || ""))) requireText(/instructions?\s+conflict|contradict|clarification|forecast/i, "Contradiction guard must explain the conflicting instructions.");
      else requireText(/column\s+was\s+not\s+found|not\s+found|did\s+not\s+substitute/i, "Missing-column guard must clearly say the requested metric was not found.");
    } else if (contract.intentType === "rank_sort" || contract.intentType === "ranking" || contract.intentType === "combination_ranking") {
      if (!tables.length) issues.push("Ranking answer must include a ranked table.");
      requireText(/top|bottom|highest|lowest|rank|sort|group/i, "Ranking answer must describe the ranking/sort logic.");
    } else if (contract.intentType === "time_trend" || contract.intentType === "time_series") {
      if (!tables.length && !charts.length) issues.push("Time-series answer must include a table or chart.");
      requireText(/month|year|week|date|time|period/i, "Time-series answer must include the time period used.");
    } else if (contract.intentType === "grouped_metric" || contract.intentType === "distribution_share") {
      if (!tables.length) issues.push("Grouped/distribution answer must include a table.");
      requireText(/group|by|count|sum|average|percent|share/i, "Grouped/distribution answer must include aggregation logic.");
    } else if (/scalar/.test(contract.intentType || "")) {
      if (!tables.length) issues.push("Scalar answer must include a compact metric table.");
      requireText(/formula|rows\s+checked|all\s+parsed\s+rows|eligible/i, "Scalar answer must include formula or rows checked.");
    } else if (contract.intentType === "pivot_matrix" && !tables.length) {
      issues.push("Matrix intent requires a result table.");
    } else if (contract.requiresTable && !tables.length && !/scalar|rate|count|metadata/.test(contract.intentType || "")) {
      issues.push("This intent requires a table result.");
    }
    if (contract.requiresChart && !charts.length) issues.push("This intent requires a chart result.");
    return {
      ok: !issues.length,
      issues: issues,
      detailedIssues: [],
      rulesRun: ["manual-intent-contract"],
      blockingRules: issues.length ? ["manual-intent-contract"] : []
    };
  }

  function extractResultSnapshot(blocks) {
    var tables = nestedTables(blocks);
    var charts = nestedCharts(blocks);
    var firstTable = tables[0] && tables[0].data && Array.isArray(tables[0].data.rows) ? tables[0].data.rows : [];
    return {
      tables: tables.length,
      charts: charts.length,
      tableHeaders: firstTable[0] || [],
      tableRows: Math.max(0, firstTable.length - 1),
      chartTypes: charts.map(function (chart) { return String(chart.data && chart.data.type || ""); }),
      chartSeries: charts.map(function (chart) { return String(chart.data && chart.data.seriesLabels || ""); })
    };
  }

  function makeValidationIssue(rule, message, severity) {
    return {
      ruleId: rule && rule.id || "unknown",
      severity: severity || (rule && rule.severity) || ANALYTICS_RULE_SEVERITY.BLOCKING,
      message: message
    };
  }

  function runAnalyticsValidators(context) {
    context = context || {};
    var issues = [];
    var rulesRun = [];
    ANALYTICS_VALIDATOR_REGISTRY.forEach(function (rule) {
      if (rule.applies && !rule.applies(context)) return;
      rulesRun.push(rule.id);
      var result = rule.validate(context);
      (Array.isArray(result) ? result : (result ? [result] : [])).forEach(function (message) {
        if (message) issues.push(makeValidationIssue(rule, message, rule.severity));
      });
    });
    var blocking = issues.filter(function (issue) { return issue.severity === ANALYTICS_RULE_SEVERITY.BLOCKING; });
    return {
      ok: !blocking.length,
      issues: issues.map(function (issue) { return issue.message; }),
      detailedIssues: issues,
      rulesRun: rulesRun,
      blockingRules: blocking.map(function (issue) { return issue.ruleId; })
    };
  }

  function blocksUseColumn(blocks, column) {
    if (!column) return false;
    var text = flattenBlockText(blocks);
    var headers = tableHeadersText(nestedTables(blocks));
    var raw = String(column || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    return new RegExp("\\b" + raw + "\\b", "i").test(headers) || new RegExp("\\b" + raw + "\\s+(count|share|by|distribution)", "i").test(text);
  }

  function tableRowsForValidation(table) {
    return table && table.data && Array.isArray(table.data.rows) ? table.data.rows : [];
  }

  function parseValidationNumber(value) {
    if (value == null) return null;
    var text = String(value).replace(/,/g, "").replace(/%/g, "").replace(/[^\d.\-]/g, "");
    if (!text || text === "-" || text === ".") return null;
    var parsed = Number(text);
    return Number.isFinite(parsed) ? parsed : null;
  }

  function isScalarIntentType(intentType) {
    return /^(scalar_metric|scalar_rate|scalar_count|sheet_shape|duplicate_count)$/i.test(String(intentType || ""));
  }

  registerAnalyticsValidator({
    id: "plan-required-blocks",
    severity: ANALYTICS_RULE_SEVERITY.BLOCKING,
    validate: function (context) {
      var planIntent = String(context.plan && context.plan.intentType || "").toLowerCase();
      var aggregation = String(context.plan && context.plan.aggregation || context.plan && context.plan.contract && context.plan.contract.aggregation || "").toLowerCase();
      if (/^(?:clarification|missing_column_guard|narrative_interpretation)$/.test(planIntent)) return "";
      if (/^(?:data_quality_diagnostic|needs_metric_clarification|needs_previous_context)$/.test(aggregation)) return "";
      var q = analyticsChartIntentText(context.question || "");
      var blocks = context.blocks || [];
      if (/\b(table|data|show|tell|find|identify|count|percentage|percent|share|matrix|rows should|columns should|top|bottom)\b/i.test(q) && !nestedTables(blocks).length) return "Execution plan requires a result table, but no table was produced.";
      if (!(context.plan && /^(pivot_matrix|cross_tab|cross_tab_count)$/.test(String(context.plan.intentType || ""))) && asksForGraph(q) && !nestedCharts(blocks).length) return "Execution plan requires a chart, but no chart was produced.";
      return "";
    }
  });
  registerAnalyticsValidator({
    id: "explicit-chart-type",
    severity: ANALYTICS_RULE_SEVERITY.BLOCKING,
    applies: function (context) {
      var q = analyticsChartIntentText(context.question || "");
      var planIntent = String(context.plan && context.plan.intentType || "").toLowerCase();
      var aggregation = String(context.plan && context.plan.aggregation || context.plan && context.plan.contract && context.plan.contract.aggregation || "").toLowerCase();
      if (/^(?:clarification|missing_column_guard|narrative_interpretation)$/.test(planIntent)) return false;
      if (/^(?:data_quality_diagnostic|needs_metric_clarification|needs_previous_context)$/.test(aggregation)) return false;
      return !(context.plan && /^(pivot_matrix|cross_tab|cross_tab_count)$/.test(String(context.plan.intentType || ""))) && !!requestedChartType(q);
    },
    validate: function (context) {
      var expected = requestedChartType(analyticsChartIntentText(context.question || ""));
      return nestedCharts(context.blocks || []).some(function (chart) { return String(chart.data && chart.data.type || "").toLowerCase() === expected.toLowerCase(); }) ? "" : "Prompt explicitly requests `" + expected + "` chart, but output chart type does not match.";
    }
  });
  registerAnalyticsValidator({
    id: "forbidden-columns",
    severity: ANALYTICS_RULE_SEVERITY.BLOCKING,
    validate: function (context) {
      return ((context.plan && context.plan.forbiddenColumns) || []).filter(function (column) {
        return blocksUseColumn(context.blocks || [], column);
      }).map(function (column) { return "Prompt forbids `" + column + "`, but output still displays/groups by it."; });
    }
  });
  registerAnalyticsValidator({
    id: "scalar-no-grouping",
    severity: ANALYTICS_RULE_SEVERITY.BLOCKING,
    applies: function (context) { return context.plan && isScalarIntentType(context.plan.intentType); },
    validate: function (context) {
      var tables = nestedTables(context.blocks || []);
      var issues = [];
      tables.forEach(function (table) {
        var rows = tableRowsForValidation(table);
        var headers = (rows[0] || []).map(function (header) { return String(header || "").toLowerCase(); });
        if (headers.some(function (header) { return /\b(group|department|gender|category|city|region|diagnosis|procedure|product|segment)\b/.test(header); }) && rows.length > 2) {
          issues.push("Scalar request produced a grouped table; scalar analytics must return one value unless grouping is explicitly requested.");
        }
      });
      var charts = nestedCharts(context.blocks || []);
      if (charts.length && !asksForGraph(context.question || "")) issues.push("Scalar request produced a chart even though no chart was requested.");
      return issues;
    }
  });
  registerAnalyticsValidator({
    id: "required-columns-present",
    severity: ANALYTICS_RULE_SEVERITY.BLOCKING,
    validate: function (context) {
      var plan = context.plan || {};
      var text = (tableHeadersText(nestedTables(context.blocks || [])) + " " + flattenBlockText(context.blocks || [])).toLowerCase();
      var issues = [];
      (plan.metrics || []).forEach(function (metric) {
        if (!metric || /^(?:__row_count|order[_\s-]*count|row[_\s-]*count|encounter[_\s-]*count|record[_\s-]*count|count|counts|rows?)$/i.test(String(metric))) return;
        if (/\b(count|how\s+many|number\s+of)\b/i.test(String(context.question || "")) && !/\b(total|sum|average|avg|mean|median|min(?:imum)?|max(?:imum)?|share|rate|percent|percentage)\b/i.test(String(context.question || ""))) return;
        if (text.indexOf(String(metric).toLowerCase()) < 0 && text.indexOf(columnLabel(metric).toLowerCase()) < 0) issues.push("Result is missing requested metric `" + metric + "`.");
      });
      (plan.dimensions || []).filter(function (dimension) {
        return !isForbiddenColumn(context.metadata || {}, context.question || "", dimension);
      }).forEach(function (dimension) {
        if (!dimension || /^(time|month|date)$/i.test(String(dimension))) return;
        if (text.indexOf(String(dimension).toLowerCase()) < 0 && text.indexOf(columnLabel(dimension).toLowerCase()) < 0) issues.push("Result is missing requested grouping column `" + dimension + "`.");
      });
      return issues;
    }
  });
  registerAnalyticsValidator({
    id: "pivot-contract",
    severity: ANALYTICS_RULE_SEVERITY.BLOCKING,
    applies: function (context) { return context.plan && /^(pivot_matrix|cross_tab|cross_tab_count)$/.test(String(context.plan.intentType || "")); },
    validate: function (context) {
      var headers = tableHeadersText(nestedTables(context.blocks || []));
      var allText = flattenBlockText(context.blocks || []);
      var pivot = context.plan && context.plan.contract && context.plan.contract.pivot || pivotMatrixIntent(context.metadata || {}, context.question || "");
      return (context.plan.dimensions || []).filter(function (dimension) {
        if (pivot && dimension === pivot.columnColumn && pivotColumnDimensionIsRepresented(context.metadata || {}, pivot, context.blocks || [])) return false;
        return String(dimension || "").toLowerCase() && !analyticsHeaderOrTextHasDimension(headers, allText, dimension);
      }).map(function (dimension) { return "Pivot/matrix output is missing requested dimension `" + dimension + "`."; });
    }
  });
  registerAnalyticsValidator({
    id: "formula-contract",
    severity: ANALYTICS_RULE_SEVERITY.BLOCKING,
    applies: function (context) {
      if (!(context.plan && context.plan.formulas && context.plan.formulas.length)) return false;
      var text = flattenBlockText(context.blocks || []);
      if (context.plan.intentType === "missing_column_guard" || /instructions?\s+conflict|contradict|clarif/i.test(text)) return false;
      return true;
    },
    validate: function (context) {
      var text = (tableHeadersText(nestedTables(context.blocks || [])) + " " + flattenBlockText(context.blocks || [])).toLowerCase();
      return (context.plan.formulas || []).filter(function (key) {
        if (key === "order_count" || key === "custom_ratio" || key === "period_change") return false;
        var label = (SHEET_FORMULA_REGISTRY[key] || {}).label || "";
        return label && text.indexOf(label.toLowerCase()) < 0;
      }).map(function (key) { return "Formula output is missing `" + SHEET_FORMULA_REGISTRY[key].label + "` from the requested formula registry."; });
    }
  });
  registerAnalyticsValidator({
    id: "formula-required-columns",
    severity: ANALYTICS_RULE_SEVERITY.BLOCKING,
    applies: function (context) {
      if (!(context.plan && context.plan.formulas && context.plan.formulas.length)) return false;
      var text = flattenBlockText(context.blocks || []);
      if (context.plan.intentType === "missing_column_guard" || /instructions?\s+conflict|contradict|clarif/i.test(text)) return false;
      return true;
    },
    validate: function (context) {
      var profile = context.plan && context.plan.schemaProfile || analyticsSchemaProfile(context.metadata || {});
      var issues = [];
      (context.plan.formulas || []).forEach(function (key) {
        var missing = formulaRequiredColumnsAvailable(profile, key);
        if (missing.length) issues.push("Cannot validate `" + (SHEET_FORMULA_REGISTRY[key] || {}).label + "` because required column type is missing: " + missing.join(", ") + ".");
      });
      return issues;
    }
  });
  registerAnalyticsValidator({
    id: "chart-table-consistency",
    severity: ANALYTICS_RULE_SEVERITY.BLOCKING,
    validate: function (context) {
      var charts = nestedCharts(context.blocks || []);
      var tables = nestedTables(context.blocks || []);
      var issues = [];
      charts.forEach(function (chart) {
        var data = chart.data || {};
        var labels = splitCSV(data.labels || "");
        var series = String(data.values || "").split(";").map(function (part) { return splitCSV(part); });
        if (labels.length > 80) issues.push("Chart has " + labels.length + " labels; high-cardinality output must be aggregated, paged, or limited.");
        series.forEach(function (values) {
          if (values.length && labels.length && values.length !== labels.length) issues.push("Chart/table consistency failed: labels count does not match values count.");
        });
        if ((data.type === "comparison" || data.type === "stackedBar") && series.length > 1 && !String(data.seriesLabels || "").trim()) issues.push("Multi-series chart is missing readable series labels.");
        if (tables.length && labels.length && series.length === 1) {
          var tableRows = tableRowsForValidation(tables[0]);
          var headers = tableRows[0] || [];
          if (headers.length >= 2) {
            var chartTitleText = String(data.title || "").toLowerCase();
            var preferredMetric = ((context.plan && context.plan.metrics || [])[0] || "").toLowerCase();
            var valueIndex = 1;
            for (var h = 1; h < headers.length; h += 1) {
              var headerText = String(headers[h] || "").toLowerCase();
              if ((preferredMetric && headerText.indexOf(preferredMetric) >= 0) ||
                (chartTitleText && chartTitleText.indexOf(headerText.replace(/\s*\([^)]*\)/g, "").trim()) >= 0) ||
                (/\b(percent|share|%)\b/.test(chartTitleText) && /\b(percent|share|%)\b/.test(headerText))) {
                valueIndex = h;
                break;
              }
            }
            var tableMap = {};
            tableRows.slice(1).forEach(function (row) {
              var label = String(row[0] || "").trim();
              var value = parseValidationNumber(row[valueIndex]);
              if (label && value !== null) tableMap[columnKey(label)] = value;
            });
            var mismatches = 0;
            labels.forEach(function (label, index) {
              var expected = tableMap[columnKey(label)];
              var actual = parseValidationNumber(series[0][index]);
              if (expected == null || actual == null) return;
              var tolerance = Math.max(0.01, Math.abs(expected) * 0.001);
              if (Math.abs(expected - actual) > tolerance) mismatches += 1;
            });
            if (mismatches) issues.push("Chart values do not reconcile with the first result table for " + mismatches + " label(s).");
          }
        }
      });
      return issues;
    }
  });
  registerAnalyticsValidator({
    id: "date-granularity",
    severity: ANALYTICS_RULE_SEVERITY.BLOCKING,
    applies: function (context) { return context.plan && context.plan.dateGranularity === "month"; },
    validate: function (context) {
      var text = flattenBlockText(context.blocks || []);
      return /\b\d{4}-\d{2}-\d{2}\b/.test(text) && !/\bjan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec\b/i.test(text) ? "Prompt requested month-level granularity, but output appears to use individual dates." : "";
    }
  });
  registerAnalyticsValidator({
    id: "identifier-guard",
    severity: ANALYTICS_RULE_SEVERITY.BLOCKING,
    validate: function (context) {
      if (context.plan && context.plan.rowLevelAllowed) return "";
      if (rankingQuestion(context.question || "")) {
        var explicitIdentifierRanking = (context.plan && context.plan.identifiers || []).some(function (column) {
          return promptMentionsColumn(context.question || "", column);
        });
        if (explicitIdentifierRanking) return "";
      }
      var headers = tableHeadersText(nestedTables(context.blocks || []));
      return (context.plan && context.plan.identifiers || []).some(function (column) {
        if (questionExplicitlyExcludesColumn(context.question || "", column)) return false;
        return headers.indexOf(String(column).toLowerCase()) >= 0;
      }) ? "Grouped analytics output must not list identifier columns unless row-level output is requested." : "";
    }
  });

  function runDeterministicAnalyticsPipeline(metadata, question, providedContract) {
    var duplicateHeaderIssue = duplicateHeaderAmbiguityBlocks(metadata, question);
    if (duplicateHeaderIssue.length) {
      var duplicateHeaderPlan = buildAnalyticsExecutionPlanWithContract(metadata, question, providedContract);
      duplicateHeaderPlan.intentType = "clarification";
      duplicateHeaderPlan.deterministicRoute = "clarificationBlocks";
      if (duplicateHeaderPlan.contract) {
        duplicateHeaderPlan.contract.intentType = "clarification";
        duplicateHeaderPlan.contract.deterministicRoute = "clarificationBlocks";
      }
      var duplicateHeaderValidation = { ok: true, issues: [], detailedIssues: [], rulesRun: ["duplicate-header-guard"], blockingRules: [] };
      var duplicateHeaderResult = makeExecutionResult(metadata, question, duplicateHeaderPlan, duplicateHeaderIssue, duplicateHeaderValidation, duplicateHeaderValidation);
      return {
        plan: duplicateHeaderPlan,
        executionPlan: duplicateHeaderPlan,
        executionResult: duplicateHeaderResult,
        blocks: duplicateHeaderIssue,
        validation: duplicateHeaderResult.validation,
        snapshot: duplicateHeaderResult.snapshot
      };
    }
    var numericTextIssue = numericTextCoercionGuardBlocks(metadata, question);
    if (numericTextIssue.length) {
      var numericTextPlan = buildAnalyticsExecutionPlanWithContract(metadata, question, providedContract);
      numericTextPlan.intentType = "clarification";
      numericTextPlan.deterministicRoute = "clarificationBlocks";
      if (numericTextPlan.contract) {
        numericTextPlan.contract.intentType = "clarification";
        numericTextPlan.contract.deterministicRoute = "clarificationBlocks";
      }
      var numericTextValidation = { ok: true, issues: [], detailedIssues: [], rulesRun: ["numeric-text-coercion-guard"], blockingRules: [] };
      var numericTextResult = makeExecutionResult(metadata, question, numericTextPlan, numericTextIssue, numericTextValidation, numericTextValidation);
      return {
        plan: numericTextPlan,
        executionPlan: numericTextPlan,
        executionResult: numericTextResult,
        blocks: numericTextIssue,
        validation: numericTextResult.validation,
        snapshot: numericTextResult.snapshot
      };
    }
    var explicitIssue = explicitManualColumnIssue(metadata, question);
    var pipelineMissingMetric = missingRequestedMetric(metadata, question);
    if (!explicitIssue && pipelineMissingMetric) explicitIssue = pipelineMissingMetric + " column was not found in this sheet.";
    if (!explicitIssue && /\bdiscount\b/i.test(String(question || "")) && !(metadata.columns || []).some(function (column) { return /\bdiscount\b/i.test(String(column || "").replace(/[_-]+/g, " ")); })) explicitIssue = "Discount column was not found in this sheet.";
    if (explicitIssue) {
      var issuePlan = buildAnalyticsExecutionPlanWithContract(metadata, question, providedContract);
      issuePlan.intentType = "missing_column_guard";
      issuePlan.deterministicRoute = "missingColumnGuardBlocks";
      if (issuePlan.contract) {
        issuePlan.contract.intentType = "missing_column_guard";
        issuePlan.contract.deterministicRoute = "missingColumnGuardBlocks";
      }
      var issueValidation = { ok: true, issues: [], detailedIssues: [], rulesRun: ["explicit-column-guard"], blockingRules: [] };
      var issueBlocks = missingColumnGuardBlocks(metadata, question);
      if (!issueBlocks.length) issueBlocks = validationFailureBlocks(question, [explicitIssue], 1);
      var issueResult = makeExecutionResult(metadata, question, issuePlan, issueBlocks, issueValidation, issueValidation);
      return {
        plan: issuePlan,
        executionPlan: issuePlan,
        executionResult: issueResult,
        blocks: issueBlocks,
        validation: issueResult.validation,
        snapshot: issueResult.snapshot
      };
    }
    var identifierGroupingIssue = highCardinalityIdentifierGroupingBlocks(metadata, question);
    if (identifierGroupingIssue.length) {
      var identifierPlan = buildAnalyticsExecutionPlanWithContract(metadata, question, providedContract);
      identifierPlan.intentType = "clarification";
      identifierPlan.deterministicRoute = "clarificationBlocks";
      if (identifierPlan.contract) {
        identifierPlan.contract.intentType = "clarification";
        identifierPlan.contract.deterministicRoute = "clarificationBlocks";
      }
      var identifierValidation = { ok: true, issues: [], detailedIssues: [], rulesRun: ["high-cardinality-identifier-guard"], blockingRules: [] };
      var identifierResult = makeExecutionResult(metadata, question, identifierPlan, identifierGroupingIssue, identifierValidation, identifierValidation);
      return {
        plan: identifierPlan,
        executionPlan: identifierPlan,
        executionResult: identifierResult,
        blocks: identifierGroupingIssue,
        validation: identifierResult.validation,
        snapshot: identifierResult.snapshot
      };
    }
    if (!providedContract && requestedChartType(question) === "scatter" && manualNumericPair(metadata, question)) {
      var scatterContract = manualIntentContract(metadata, question);
      scatterContract.intentType = "relationship_scatter";
      scatterContract.deterministicRoute = "manualRelationshipBlocks";
      scatterContract.route = "manualRelationshipBlocks";
      scatterContract.requiresChart = true;
      scatterContract.chartType = "scatter";
      var scatterPlan = buildAnalyticsExecutionPlanWithContract(metadata, question, scatterContract);
      var scatterValidation = validateExecutionPlan(metadata, question, scatterPlan);
      var scatterBlocks = scatterValidation.ok ? manualRelationshipBlocks(metadata, question) : [];
      var scatterContractValidation = scatterValidation.ok ? validateManualIntentContractResult(metadata, question, scatterContract, scatterBlocks) : { ok: false, issues: scatterValidation.issues, detailedIssues: [], rulesRun: ["execution-plan"], blockingRules: ["execution-plan"] };
      var scatterRegistryValidation = scatterValidation.ok && scatterContractValidation.ok ? runAnalyticsValidators({ metadata: metadata, question: question, plan: scatterPlan, blocks: scatterBlocks }) : { ok: true, issues: [], detailedIssues: [], rulesRun: [], blockingRules: [] };
      var scatterResultValidation = {
        ok: !!(scatterContractValidation.ok && scatterRegistryValidation.ok),
        issues: (scatterContractValidation.issues || []).concat(scatterRegistryValidation.issues || []),
        detailedIssues: (scatterContractValidation.detailedIssues || []).concat(scatterRegistryValidation.detailedIssues || []),
        rulesRun: (scatterContractValidation.rulesRun || []).concat(scatterRegistryValidation.rulesRun || []),
        blockingRules: (scatterContractValidation.blockingRules || []).concat(scatterRegistryValidation.blockingRules || [])
      };
      var scatterResult = makeExecutionResult(metadata, question, scatterPlan, scatterBlocks, scatterResultValidation, scatterValidation);
      return { plan: scatterPlan, executionPlan: scatterPlan, executionResult: scatterResult, blocks: scatterBlocks, validation: scatterResult.validation, snapshot: scatterResult.snapshot };
    }
    var plan = buildAnalyticsExecutionPlanWithContract(metadata, question, providedContract);
    var contract = plan.contract || manualIntentContract(metadata, question);
    var planValidation = validateExecutionPlan(metadata, question, plan);
    var blocks = planValidation.ok ? executeManualIntentContract(metadata, question, contract) : [];
    if (!planValidation.ok) {
      blocks = executeManualIntentContract(metadata, question, contract) || [];
      if (!blocks.length) blocks = manualDeterministicBlocks(metadata, question) || [];
      if (!blocks.length) blocks = deterministicNarrativeSummaryBlocks(metadata, question) || [];
    }
    var contractValidation = blocks.length ? validateManualIntentContractResult(metadata, question, contract, blocks) : { ok: false, issues: planValidation.issues, detailedIssues: [], rulesRun: ["execution-plan"], blockingRules: ["execution-plan"] };
    var registryValidation = blocks.length && contractValidation.ok ? runAnalyticsValidators({ metadata: metadata, question: question, plan: plan, blocks: blocks }) : { ok: true, issues: [], detailedIssues: [], rulesRun: [], blockingRules: [] };
    var validation = {
      ok: !!(contractValidation.ok && registryValidation.ok),
      issues: (contractValidation.issues || []).concat(registryValidation.issues || []),
      detailedIssues: (contractValidation.detailedIssues || []).concat(registryValidation.detailedIssues || []),
      rulesRun: (contractValidation.rulesRun || []).concat(registryValidation.rulesRun || []),
      blockingRules: (contractValidation.blockingRules || []).concat(registryValidation.blockingRules || [])
    };
    var executionResult = makeExecutionResult(metadata, question, plan, blocks, validation, planValidation);
    return {
      plan: plan,
      executionPlan: plan,
      executionResult: executionResult,
      blocks: blocks,
      validation: executionResult.validation,
      snapshot: executionResult.snapshot
    };
  }

  function runDeterministicAnalyticsPipelineAsync(metadata, question, onStep, providedContract) {
    function step(message) {
      if (typeof onStep === "function") onStep(message);
      return yieldToBrowser();
    }
    var plan;
    var blocks;
    var validation;
    var contract;
    return step("Preparing deterministic analytics plan").then(function () {
      var duplicateHeaderIssue = duplicateHeaderAmbiguityBlocks(metadata, question);
      if (duplicateHeaderIssue.length) {
        plan = buildAnalyticsExecutionPlanWithContract(metadata, question, providedContract);
        plan.intentType = "clarification";
        plan.deterministicRoute = "clarificationBlocks";
        contract = plan.contract || {};
        contract.intentType = "clarification";
        contract.deterministicRoute = "clarificationBlocks";
        validation = { ok: true, issues: [], detailedIssues: [], rulesRun: ["duplicate-header-guard"], blockingRules: [] };
        blocks = duplicateHeaderIssue;
        return step("Clarified duplicate header ambiguity");
      }
      var numericTextIssue = numericTextCoercionGuardBlocks(metadata, question);
      if (numericTextIssue.length) {
        plan = buildAnalyticsExecutionPlanWithContract(metadata, question, providedContract);
        plan.intentType = "clarification";
        plan.deterministicRoute = "clarificationBlocks";
        contract = plan.contract || {};
        contract.intentType = "clarification";
        contract.deterministicRoute = "clarificationBlocks";
        validation = { ok: true, issues: [], detailedIssues: [], rulesRun: ["numeric-text-coercion-guard"], blockingRules: [] };
        blocks = numericTextIssue;
        return step("Clarified numeric text coercion handling");
      }
      var explicitIssue = explicitManualColumnIssue(metadata, question);
      if (explicitIssue) {
        plan = buildAnalyticsExecutionPlanWithContract(metadata, question, providedContract);
        validation = { ok: false, issues: [explicitIssue], detailedIssues: [], rulesRun: ["explicit-column-guard"], blockingRules: ["explicit-column-guard"] };
        return step("Stopped before calculation because a requested column is missing");
      }
      plan = buildAnalyticsExecutionPlanWithContract(metadata, question, providedContract);
      contract = plan.contract || manualIntentContract(metadata, question);
      validation = validateExecutionPlan(metadata, question, plan);
      if (!validation.ok) return step("Execution plan needs clarification");
      return step("Computing exact " + (contract.intentType || "manual") + " analytics from all parsed rows");
    }).then(function () {
      if (blocks && blocks.length) {
        return step("Post-processing analytics blocks");
      }
      if (validation && !validation.ok) {
        blocks = executeManualIntentContract(metadata, question, contract) || [];
        if (!blocks.length) blocks = manualDeterministicBlocks(metadata, question) || [];
        if (!blocks.length) blocks = deterministicNarrativeSummaryBlocks(metadata, question) || [];
        return step(blocks.length ? "Recovered deterministic analytics answer" : "Stopped before calculation because the analytics plan is ambiguous");
      }
      blocks = executeManualIntentContract(metadata, question, contract);
      return step("Post-processing analytics blocks");
    }).then(function () {
      var planValidation = validation && !validation.ok ? validation : validateExecutionPlan(metadata, question, plan);
      var contractValidation = blocks && blocks.length ? validateManualIntentContractResult(metadata, question, contract, blocks) : { ok: false, issues: planValidation.issues, detailedIssues: [], rulesRun: ["execution-plan"], blockingRules: ["execution-plan"] };
      var registryValidation = blocks && blocks.length && contractValidation.ok ? runAnalyticsValidators({ metadata: metadata, question: question, plan: plan, blocks: blocks }) : { ok: true, issues: [], detailedIssues: [], rulesRun: [], blockingRules: [] };
      var blockValidation = {
        ok: !!(contractValidation.ok && registryValidation.ok),
        issues: (contractValidation.issues || []).concat(registryValidation.issues || []),
        detailedIssues: (contractValidation.detailedIssues || []).concat(registryValidation.detailedIssues || []),
        rulesRun: (contractValidation.rulesRun || []).concat(registryValidation.rulesRun || []),
        blockingRules: (contractValidation.blockingRules || []).concat(registryValidation.blockingRules || [])
      };
      validation = makeExecutionResult(metadata, question, plan, blocks, blockValidation, planValidation).validation;
      return step("Validating analytics answer");
    }).then(function () {
      var executionResult = makeExecutionResult(metadata, question, plan, blocks, validation.result || validation, validation.plan);
      return {
        plan: plan,
        executionPlan: plan,
        executionResult: executionResult,
        blocks: blocks,
        validation: executionResult.validation,
        snapshot: executionResult.snapshot
      };
    });
  }

  function yieldToBrowser() {
    return new Promise(function (resolve) {
      if (typeof requestAnimationFrame === "function") requestAnimationFrame(function () { setTimeout(resolve, 0); });
      else setTimeout(resolve, 0);
    });
  }

  function rowsForAnalytics(metadata) {
    return metadata ? (metadata._localRows || metadata.draftRows || []) : [];
  }

  function isMetadataManualQuestion(question, metadata) {
    var q = String(question || "");
    if (dateComparisonRowCountQuestion(q)) return false;
    if (duplicateDetailIntent(q) || duplicateCountIntent(q)) return false;
    if (/\b(total|sum|average|avg|mean|median|min(?:imum)?|max(?:imum)?)\b/i.test(q) && mentionedNumericColumns(metadata || {}, q).length) return false;
    if ((mentionedNumericColumns(metadata || {}, q).length || manualMetricColumn(metadata || {}, q)) && (promptGroupColumns(metadata || {}, q, resolveGroupColumn(metadata || {}, q)).length || executionRequestsGrouping(q))) return false;
    return /\b(rows?\s+and\s+columns?|columns?\s+and\s+rows?|(?:row|rows?|record|records?)\s+count\s+and\s+(?:column|columns?|field|fields?)\s+count|(?:column|columns?|field|fields?)\s+count\s+and\s+(?:row|rows?|record|records?)\s+count|sheet\s+shape|dataset\s+shape|how\s+many\s+columns|how\s+many\s+rows|column\s+names?|columns?\s+(?:are|in|available|present)|file\s+name|missing|blank|null|empty)\b/i.test(q)
  }

  function metadataManualExecutionPlan(question) {
    return {
      version: analyticsEngineVersion(),
      intentType: "metadata_summary",
      deterministicRoute: "scalarManualCheckBlocks",
      chartType: "",
      dimensions: [],
      metrics: [],
      formulas: [],
      aggregation: "metadata_lookup",
      filters: [],
      sort: {},
      limit: 0,
      rowLevelAllowed: false,
      identifiers: [],
      contract: {
        intentType: "metadata_summary",
        requiresChart: false,
        requiresTable: true,
        rowLevelAllowed: false,
        prompt: String(question || "")
      }
    };
  }

  function manualAnalysisPlan(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    var lower = q.toLowerCase();
    if (manualNarrativeInterpretationIntent(q)) return { level: "interpretation", credits: 5 };
    if (manualPromptNeedsDatasetGuidance(q) || !manualPromptLooksDatasetRelated(metadata || {}, q)) return { level: "clarification", credits: 0 };
    if (missingRequestedMetric(metadata || {}, q) || ambiguousManualAnalyticsPrompt(metadata || {}, q)) return { level: "validation", credits: 0 };
    if (duplicateDetailIntent(q)) return { level: "row-detail", credits: 6 };
    if (groupedMissingValuesRequested(q)) return { level: groupedMissingValuesColumn(metadata || {}, q) ? "grouped-missing-values" : "validation", credits: groupedMissingValuesColumn(metadata || {}, q) ? 9 : 0 };
    if (isMetadataManualQuestion(q, metadata || {})) return { level: "metadata", credits: 2 };
    if (dateComparisonRowCountQuestion(q)) return { level: "date-logic", credits: rows.length > 5000 ? 6 : 4 };
    if (/\bverify\b/i.test(q) && /\bmismatches?\b/i.test(q)) return { level: "formula-validation", credits: rows.length > 5000 ? 11 : 9 };
    if (/\b(where|only\s+for|only|for)\b/i.test(q) && /\b(total|sum|average|avg|mean|count|how\s+many)\b/i.test(q)) return { level: "filtered-local", credits: rows.length > 5000 ? 9 : 7 };
    if (/\b(q1|quartile|q3|iqr)\b/i.test(q) && !/\b(outlier|above\s+q3)\b/i.test(q)) return { level: "distribution-local", credits: rows.length > 5000 ? 9 : 7 };
    if (/\b(share|contribution|percentage\s+of\s+total|percent\s+of\s+total)\b/i.test(q) && executionRequestsGrouping(q)) return { level: "share-local", credits: asksForGraph(q) ? 10 : 9 };
    if (/\b(rate|percentage|percent|%)\b/i.test(q) && executionRequestsGrouping(q)) return { level: "rate-local", credits: rows.length > 5000 ? 10 : 8 };
    if (/\b(correlation|correlate|pearson)\b/i.test(q) || /\bq3\s*\+\s*1\.5\s*[x×*]\s*iqr|iqr\b/i.test(q) || /\bmore\s+than\s+one\s+encounter|repeat\s+patients?|unique|distinct\b/i.test(q)) return { level: "advanced-local", credits: rows.length > 5000 ? 12 : 9 };
    if (/\b(top\s+\d+|bottom\s+\d+|highest|lowest|largest|smallest|rank|ranking|from\s+highest\s+to\s+lowest)\b/i.test(q)) return { level: "ranking", credits: asksForGraph(q) ? 11 : 9 };
    if (/\b(matrix|pivot)\b/i.test(q) && !pivotMatrixIntent(metadata || {}, q)) return { level: "intermediate", credits: 11 };
    if (pivotMatrixIntent(metadata || {}, q)) return { level: "matrix", credits: rows.length > 5000 ? 15 : 12 };
    var deep = !!derivedMetricSpec(metadata || {}, q) || rows.length > 1000 || /\b(all|complete|entire|full|every|accurate|exact|deep|full\s+dataset|complete\s+data)\b/i.test(q);
    var intermediate = !deep && (asksForGraph(q) || /\b(group|grouped|wise|by|compare|trend|monthly|aggregate|sum|average|count)\b/i.test(q));
    if (deep) return { level: "deep", credits: rows.length > 5000 ? 20 : 15 };
    if (intermediate) return { level: "intermediate", credits: asksForGraph(q) ? 11 : 9 };
    return { level: "normal", credits: 6 };
  }

  function comparableManualIntent(intent) {
    intent = String(intent || "").toLowerCase();
    if (/^(grouped_analytics|grouped_metric|grouped_count)$/.test(intent)) return "grouped";
    if (/^(scalar_metric|scalar_count|scalar_rate)$/.test(intent)) return "scalar";
    if (/^(cross_tab|cross_tab_count|pivot_matrix)$/.test(intent)) return "pivot";
    return intent;
  }

  function comparableManualAggregation(aggregation) {
    aggregation = String(aggregation || "").toLowerCase();
    if (/^(sum|total)$/.test(aggregation)) return "sum";
    if (/^(avg|average|mean)$/.test(aggregation)) return "average";
    if (/^(count|row_count|filtered_count|count_cross_tab)$/.test(aggregation)) return "count";
    if (/^(min|max|median|rate|share_percent)$/.test(aggregation)) return aggregation;
    return aggregation;
  }

  function manualContractsCompatible(typed, stored) {
    typed = typed || {};
    stored = stored || {};
    var typedIntent = comparableManualIntent(typed.intentType);
    var storedIntent = comparableManualIntent(stored.intentType);
    if (typedIntent && storedIntent && typedIntent !== storedIntent) return false;
    var typedMetric = columnKey(typed.metric || "");
    var storedMetric = columnKey(stored.metric || "");
    if (typedMetric && typedMetric !== "__row_count" && storedMetric && storedMetric !== typedMetric) return false;
    var typedGroup = columnKey(typed.groupBy || "");
    var storedGroup = columnKey(stored.groupBy || "");
    if (!typedGroup && storedGroup) return false;
    if (typedGroup && storedGroup && typedGroup !== storedGroup) return false;
    var typedAgg = comparableManualAggregation(typed.aggregation || "");
    var storedAgg = comparableManualAggregation(stored.aggregation || "");
    if (typedAgg && storedAgg && typedAgg !== storedAgg) return false;
    return true;
  }

  function manualContractCreditEstimate(metadata, question, contract) {
    metadata = metadata || {};
    contract = contract || manualIntentContract(metadata, question);
    var modeContract = promptModeContract(question);
    var intent = String(contract && contract.intentType || "").toLowerCase();
    var rows = rowsForAnalytics(metadata);
    var rowCount = rows.length || Number(metadata.rowCount || 0);
    var credits = 0;
    if (/^(clarification|missing_column_guard)$/.test(intent)) credits = 0;
    else if (/^(metadata_summary|sheet_shape|metadata_mixed|missing_values|duplicate_count)$/.test(intent)) credits = 2;
    else if (/^(duplicate_detail|distinct_count|repeat_count|distinct_repeat_count|date_logic)$/.test(intent)) credits = rowCount > 5000 ? 6 : 4;
    else if (/^(narrative_interpretation)$/.test(intent)) credits = 5;
    else if (/^(correlation|iqr_outlier_count|distribution_stats)$/.test(intent)) credits = rowCount > 5000 ? 12 : 9;
    else if (/^(cross_tab|cross_tab_count|pivot_matrix)$/.test(intent)) credits = rowCount > 5000 ? 15 : 12;
    else if (/^(formula_validation)$/.test(intent)) credits = rowCount > 5000 ? 11 : 9;
    else if (/^(grouped_rate|grouped_share)$/.test(intent)) credits = rowCount > 5000 ? 10 : 8;
    else if (/^(scalar_metric|scalar_count|scalar_rate|grouped_analytics|grouped_metric|grouped_count|filtered_aggregation|filtered_count|threshold_count|ranking|rank_sort|time_series|time_trend)$/.test(intent)) credits = rowCount > 5000 ? 9 : 7;
    else credits = manualAnalysisPlan(metadata, question).credits || 0;
    return {
      credits: applyPromptModeCreditCap(credits, modeContract, 25),
      level: intent || "manual",
      modes: modeContract.modes || []
    };
  }

  function canonicalManualAnalyticsDecision(metadata, question, providedContract) {
    metadata = metadata || {};
    var q = String(question || "").replace(/\s+/g, " ").trim();
    var liveContract = manualIntentContract(metadata, q);
    var storedContract = providedContract ? normalizeStoredIntentContract(metadata, q, providedContract) : null;
    var contract = storedContract && manualContractsCompatible(liveContract, storedContract) ? storedContract : liveContract;
    if (!contract) contract = liveContract || {};
    var route = contract.deterministicRoute || routeForStoredIntentContract(contract) || "";
    if (route && !contract.deterministicRoute) contract.deterministicRoute = route;
    var estimate = manualContractCreditEstimate(metadata, q, contract);
    var intent = String(contract.intentType || "").toLowerCase();
    var action = "local";
    if (intent === "narrative_interpretation" || manualNarrativeInterpretationIntent(q)) action = "ai_narrative";
    else if (intent === "clarification" || manualPromptNeedsDatasetGuidance(q) || !manualPromptLooksDatasetRelated(metadata, q)) action = "clarify";
    else if (intent === "missing_column_guard" || explicitManualColumnIssue(metadata, q)) action = "block";
    return {
      question: q,
      contract: contract,
      route: route,
      billing: estimate,
      action: action,
      source: storedContract && contract === storedContract ? "provided_contract" : "live_classifier"
    };
  }

  function promptSuggestionSeed(value) {
    var text = String(value || "");
    var hash = 0;
    for (var i = 0; i < text.length; i += 1) hash = ((hash << 5) - hash + text.charCodeAt(i)) | 0;
    return Math.abs(hash);
  }

  function promptSuggestionTone(question, index) {
    var colors = ["#2563eb", "#16a34a", "#f59e0b", "#dc2626", "#7c3aed", "#0891b2", "#db2777", "#65a30d", "#0f766e", "#9333ea"];
    var accent = colors[(promptSuggestionSeed(question) + index) % colors.length];
    var hex = accent.replace("#", "");
    var r = parseInt(hex.slice(0, 2), 16) || 0;
    var g = parseInt(hex.slice(2, 4), 16) || 0;
    var b = parseInt(hex.slice(4, 6), 16) || 0;
    var luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
    var text = luminance > 0.58 ? "#1e293b" : accent;
    return {
      background: "rgba(" + r + "," + g + "," + b + ",.12)",
      hover: "rgba(" + r + "," + g + "," + b + ",.18)",
      border: "rgba(" + r + "," + g + "," + b + ",.28)",
      accent: accent,
      color: text
    };
  }

  function estimateManualPromptCredits(metadata, question) {
    if (explicitManualColumnIssue(metadata || {}, question)) return { credits: 0, level: "validation", modes: [] };
    var modeContract = promptModeContract(question);
    var contract = manualIntentContract(metadata || {}, question);
    var intent = String(contract && contract.intentType || "").toLowerCase();
    if (intent && intent !== "general") {
      return manualContractCreditEstimate(metadata || {}, question, contract);
    }
    var plan = manualAnalysisPlan(metadata, question);
    if (plan.level === "clarification") return { credits: 0, level: plan.level, modes: [] };
    if (plan.level === "interpretation") return { credits: plan.credits, level: plan.level, modes: [] };
    if (plan.level === "metadata") return { credits: plan.credits, level: plan.level, modes: [] };
    var credits = applyPromptModeCreditCap(plan.credits, modeContract, 25);
    return { credits: credits, level: plan.level, modes: modeContract.modes || [] };
  }

  function columnLabel(column) {
    return String(column || "").trim().replace(/[_-]+/g, " ");
  }

  function exactSemanticColumn(metadata, words) {
    return findColumnByWords(metadata || {}, words || "");
  }

  function nonBucketComparisonIntent(metadata, question) {
    var q = String(question || "");
    var match = q.match(/\b([A-Za-z][A-Za-z0-9_-]{1,40})\s+vs\s+non[-\s]*([A-Za-z][A-Za-z0-9_-]{1,40})\b/i);
    if (!match || columnKey(match[1]) !== columnKey(match[2])) return null;
    var requested = columnKey(match[1]);
    var categorical = (metadata && metadata.categoricalColumns || []).slice();
    for (var i = 0; i < categorical.length; i += 1) {
      var column = categorical[i];
      var values = uniqueColumnValues(metadata, column, 500);
      for (var v = 0; v < values.length; v += 1) {
        if (columnKey(values[v]) === requested) {
          return { column: column, value: values[v] };
        }
      }
    }
    return { column: "", value: match[1] };
  }

  function suggestionPromptAvailability(metadata, prompt) {
    metadata = metadata || {};
    var q = String(prompt || "");
    var profile = analyticsSchemaProfile(metadata);
    var plan = buildAnalyticsExecutionPlan(metadata, q);
    var capabilities = plan.routeCapabilities || deterministicRouteCapabilities(metadata);
    var missing = [];
    function requireColumn(regex, label, words) {
      if (!regex.test(q)) return;
      var semanticKey = "";
      if (/revenue/i.test(label)) semanticKey = "revenue";
      else if (/profit/i.test(label)) semanticKey = "profit";
      else if (/discount/i.test(label)) semanticKey = "discount";
      else if (/units/i.test(label)) semanticKey = "quantity";
      else if (/payment/i.test(label)) semanticKey = "payment";
      else if (/customer/i.test(label)) semanticKey = "customer_type";
      else if (/drug|product/i.test(label)) semanticKey = "product";
      else if (/manufacturer/i.test(label)) semanticKey = "manufacturer";
      else if (/region/i.test(label)) semanticKey = "region";
      if (semanticKey && profile.semantics[semanticKey] && profile.semantics[semanticKey].column) return;
      if (!exactSemanticColumn(metadata, words)) missing.push(label + " column");
    }
    requireColumn(/\brevenue\b|\bsales\b/i, "Revenue", ["revenue", "sales", "amount", "total_revenue"]);
    requireColumn(/\bprofit\b/i, "Profit", ["profit", "net_profit", "gross_profit", "marginamount"]);
    requireColumn(/\bdiscount\b/i, "Discount", ["discount", "discount_amount", "discountvalue"]);
    requireColumn(/\bunits?\s+sold\b|\bquantity\b|\bqty\b|\bper\s+unit\b|\brevenue\s+per\s+unit\b|\brevenue\s*\/\s*units?\b/i, "Units Sold", ["units sold", "quantity", "qty", "units", "total_quantity", "totalquantity"]);
    requireColumn(/\bpayment\s*method\b|\bpaymentmethod\b/i, "Payment Method", ["paymentmethod", "payment_method", "payment", "method"]);
    requireColumn(/\bcustomer\s+type\b/i, "Customer Type", ["customer type", "customer_type", "client type", "buyer type", "customer segment", "segment", "channel"]);
    requireColumn(/\bcustomer\s+segment\b/i, "Customer Segment", ["customer segment", "segment", "customer type", "customer_type", "channel"]);
    requireColumn(/\bdrug\s+name\b|\bdrug\b|\bproduct\b/i, "Drug/Product", ["drug name", "drug", "medicine", "product", "productname", "item", "sku"]);
    requireColumn(/\bmanufacturer\b/i, "Manufacturer", ["manufacturer", "maker", "brand", "company", "supplier", "vendor"]);
    requireColumn(/\bregion\b/i, "Region", ["region", "territory", "market", "country", "state", "city", "location"]);
    requireColumn(/\bdosage\s+form\b/i, "Dosage Form", ["dosage form", "form"]);
    if (asksForTimeBreakdown(q) && !((metadata.dateColumns || [])[0] || exactSemanticColumn(metadata, ["sale date", "date", "month", "created_at", "updated_at"]))) {
      missing.push("Date/Sale Date column");
    }
    var bucket = nonBucketComparisonIntent(metadata, q);
    if (/\bvs\s+non[-\s]/i.test(q) && (!bucket || !bucket.column)) {
      missing.push("categorical value `" + (bucket && bucket.value || "requested value") + "`");
    }
    (plan.formulas || []).forEach(function (key) {
      formulaRequiredColumnsAvailable(profile, key).forEach(function (semanticKey) {
        missing.push((SHEET_FORMULA_REGISTRY[key] || {}).label + " dependency `" + semanticKey + "`");
      });
    });
    if (/\b(matrix|pivot|rows?\s+are|columns?\s+are)\b/i.test(q) && !capabilities.pivotMatrix) missing.push("pivot/matrix deterministic route");
    if (/\b(month|monthly|quarter|quarterly|trend|over\s+time)\b/i.test(q) && !capabilities.timeSeries) missing.push("time-series deterministic route");
    if (/\b(outlier|risky|unusually|anomal)\b/i.test(q) && !capabilities.rowOutliers) missing.push("row-level outlier deterministic route");
    return {
      ok: !missing.length,
      issues: missing.map(function (item) { return "Suggestion requires missing " + item + "."; })
    };
  }

  function suggestionRequiredIntentTerms(question) {
    var q = String(question || "");
    var terms = [];
    function add(name, regex) {
      if (regex.test(q) && terms.indexOf(name) < 0) terms.push(name);
    }
    add("average", /\b(avg|average|mean)\b/i);
    add("per_unit", /\b(per\s+unit|revenue\s*\/\s*units?|revenue\s+per\s+unit|average\s+revenue\s+per\s+unit)\b/i);
    add("units_sold", /\b(units?\s+sold|quantity|qty)\b/i);
    add("aov", /\b(aov|average\s+order\s+value)\b/i);
    add("profit_margin", /\b(profit\s+margin|margin\s*%)\b/i);
    add("discount_percent", /\b(discount\s*%|discount\s+percentage)\b/i);
    add("share_contribution", /\b(share|contribution|percent\s+contribution|mix|distribution)\b/i);
    add("net_revenue", /\b(net\s+revenue|after\s+discount)\b/i);
    add("pivot_matrix", /\b(matrix|pivot|heatmap|rows?\s+are|columns?\s+are|rows?\s+should\s+be|columns?\s+should\s+be)\b/i);
    add("outlier", /\b(outlier|unusually|risky|anomaly|anomalies)\b/i);
    add("top_bottom", /\b(top\s+\d+|bottom\s+\d+|highest|lowest|least|most)\b/i);
    add("rank_sort", /\b(rank|ranking|sort|sorted|highest\s+to\s+lowest|lowest\s+to\s+highest)\b/i);
    add("filter_condition", /\b(where|only\s+where|ignore|exclude|fewer\s+than|at\s+least|above|below|greater|less|increased|decreased|previous)\b/i);
    add("monthly", /\b(month|monthly|month-wise|monthwise|over\s+time|time\s+series|trend)\b/i);
    add("quarterly", /\b(quarter|quarterly)\b/i);
    add("bucket_vs_non", /\bvs\s+non[-\s]/i);
    add("kpi", /\b(kpi|dashboard)\b/i);
    return terms;
  }

  function promptRequestsGrouping(question) {
    var q = String(question || "").replace(/\b(?:do\s+not|don't|without|no)\s+(?:group|grouping|break\s*down|breakdown)\s+by\b[^.;]*/ig, " ");
    if (/\b(matrix|pivot|heatmap|rows?\s+are|columns?\s+are|rows?\s+should\s+be|columns?\s+should\s+be|top\s+\d+|bottom\s+\d+|rank|ranking|distribution|share|mix|break\s*down|breakdown|group(?:ed)?\s+by|split\s+by|compare\s+by|for\s+each|wise|across)\b/i.test(q)) return true;
    if (/\bby\s+[`'"]?[A-Za-z][A-Za-z0-9_ -]{1,60}\b/i.test(q)) return true;
    return false;
  }

  function scalarAnalyticsIntent(metadata, question) {
    metadata = metadata || {};
    var q = String(question || "");
    var lower = q.toLowerCase();
    if (!q || promptRequestsGrouping(q) || asksForTimeBreakdown(q) || pivotMatrixIntent(metadata, q) || combinationRankingIntent(metadata, q)) return null;
    if (/\b(outlier|unusually|anomal|risky|dashboard|kpi)\b/i.test(q)) return null;
    function metricColumn() {
      if (/\btreatment\s+cost\b/i.test(q)) return findColumnByWords(metadata, ["treatment cost", "treatmentcost", "treatmentcost_inr"]);
      if (/\bmedication\s+cost\b/i.test(q)) return findColumnByWords(metadata, ["medication cost", "medicationcost", "medicationcost_inr"]);
      if (/\blab\s+cost\b/i.test(q)) return findColumnByWords(metadata, ["lab cost", "labcost", "labcost_inr"]);
      if (/\binsurance\s+(claim|coverage)\b/i.test(q)) return findColumnByWords(metadata, ["insurance claim", "insuranceclaim", "insuranceclaim_inr", "insurance coverage", "insurancecoverage"]);
      if (/\b(wait\s*time)\b/i.test(q)) return findColumnByWords(metadata, ["wait time", "waittimeminutes", "waittime"]);
      if (/\b(length\s+of\s+stay|los)\b/i.test(q)) return findColumnByWords(metadata, ["length of stay", "lengthofstaydays", "los"]);
      if (/\bsatisfaction\b/i.test(q)) return findColumnByWords(metadata, ["satisfaction score", "satisfactionscore", "satisfaction"]);
      if (/\bage\b/i.test(q)) return findColumnByWords(metadata, ["age", "patient age"]);
      if (/\b(bill|billing|hospital billing|totalbill|revenue|sales|amount)\b/i.test(q)) return revenueColumn(metadata);
      if (/\bprofit\b/i.test(q)) return profitColumn(metadata);
      if (/\bdiscount\b/i.test(q)) return discountColumn(metadata);
      if (/\b(units?\s+sold|quantity|qty)\b/i.test(q)) return quantityColumn(metadata);
      return manualMetricColumn(metadata, q);
    }
    function explicitBlankValueCountColumn() {
      if (!/\b(values?|categories?|distribution|counts?)\b/i.test(q) || !/\b(blanks?|missing|null|empty)\b/i.test(q)) return "";
      var column = resolveQuestionColumn(metadata, q, "category");
      return column && columnKey(q).indexOf(columnKey(column)) >= 0 ? column : "";
    }
    if (/\brows?\s+and\s+columns?|columns?\s+and\s+rows?|sheet\s+shape|dataset\s+shape\b/i.test(q)) {
      return {
        title: "Schema-safe prompt",
        prompt: q + " Use the parsed workbook metadata and return only row count and column count.",
        reason: "Keeps schema questions as metadata answers."
      };
    }
    if (/\bduplicate|duplicates\b/i.test(q)) {
      return {
        title: "Data-quality prompt",
        prompt: q + " Use all parsed rows and return the exact duplicate row count only.",
        reason: "Keeps duplicate checks as data-quality answers."
      };
    }
    var blankValueColumn = explicitBlankValueCountColumn();
    if (blankValueColumn) {
      return {
        title: "Value-count prompt",
        prompt: "Count values in `" + blankValueColumn + "` including blanks. Use all parsed rows, label blank/null/empty cells as `Blank`, and return Count and Percent.",
        reason: "Counts values inside the requested column instead of switching to column-wise missing-value checks."
      };
    }
    if (/\bmissing|blank|null|empty\b/i.test(q) && /\bvalues?|column-wise|columns?\b/i.test(q)) {
      return {
        title: "Missing-values prompt",
        prompt: q + " Use all parsed rows and return a column-wise missing-value table. Include blank/null/empty cells as missing.",
        reason: "Keeps missing-value checks column-wise."
      };
    }
    if (/\b(readmission|icu|mortality|death|rejection|reject)\b/i.test(q) && /\b(rate|%)\b/i.test(q)) {
      var rateColumn = /\breadmission/i.test(q) ? findColumnByWords(metadata, ["readmission", "readmitted", "readmission30days"]) :
        /\bicu\b/i.test(q) ? findColumnByWords(metadata, ["icu", "isicu"]) :
          /\b(rejection|reject)\b/i.test(q) ? findColumnByWords(metadata, ["claim status", "claimstatus"]) :
            findColumnByWords(metadata, ["outcome", "status"]);
      return {
        title: "Scalar rate prompt",
        prompt: q + " Use all parsed rows and return one percentage value. Formula: matching rows" + (rateColumn ? " from `" + rateColumn + "`" : "") + " / eligible encounter count x 100. Do not group by any category.",
        reason: "Keeps rate questions as one scalar percentage."
      };
    }
    var metric = metricColumn();
    if (/\b(min(?:imum)?|lowest|least)\b/i.test(q) && !/\b(max(?:imum)?|highest|range)\b/i.test(q) && metric) {
      return {
        title: "Scalar minimum prompt",
        prompt: q + " Use MIN `" + metric + "` over all parsed rows and return one minimum value. Do not group by any category.",
        reason: "Keeps minimum questions as one scalar answer."
      };
    }
    if (/\b(max(?:imum)?|highest|most)\b/i.test(q) && !/\b(min(?:imum)?|lowest|least|range)\b/i.test(q) && metric) {
      return {
        title: "Scalar maximum prompt",
        prompt: q + " Use MAX `" + metric + "` over all parsed rows and return one maximum value. Do not group by any category.",
        reason: "Keeps maximum questions as one scalar answer."
      };
    }
    if (/\b(range)\b/i.test(q) || (/\b(min(?:imum)?|lowest|least)\b/i.test(q) && /\b(max(?:imum)?|highest|most)\b/i.test(q) && metric)) {
      return {
        title: "Scalar range prompt",
        prompt: q + " Use all parsed rows and return MIN `" + metric + "` and MAX `" + metric + "` only. Do not group by any category.",
        reason: "Keeps range questions as scalar min/max answers."
      };
    }
    if (/\bmedian\b/i.test(q) && metric) {
      return {
        title: "Scalar median prompt",
        prompt: q + " Use all parsed rows and return MEDIAN `" + metric + "` as one value. Do not group by any category.",
        reason: "Keeps median questions as one scalar answer."
      };
    }
    if (/\b(avg|average|avrage|averge|mean)\b/i.test(q) && metric) {
      return {
        title: "Scalar average prompt",
        prompt: q + " Use AVG `" + metric + "` over all parsed rows and return one value. Do not group by any category.",
        reason: "Keeps average questions as one scalar answer."
      };
    }
    if (/\b(total|totl|tatal|sum)\b/i.test(q) && metric) {
      return {
        title: "Scalar total prompt",
        prompt: q + " Use SUM `" + metric + "` over all parsed rows and return one total value. Do not group by any category.",
        reason: "Keeps total questions as one scalar answer."
      };
    }
    if (/\b(count|how many|number of)\b/i.test(lower)) {
      if (/\b(?:row|rows|record|records)\s+count\b/i.test(q) && /\b(?:column|columns|field|fields)\s+count\b/i.test(q)) {
        return {
          title: "Schema-safe prompt",
          prompt: q + " Use parsed workbook metadata only and return row count and column count.",
          reason: "Keeps row/column count questions as metadata answers."
        };
      }
      return {
        title: "Scalar count prompt",
        prompt: q + " Use all parsed rows and return one count value. Do not group unless the prompt explicitly names a grouping column.",
        reason: "Keeps count questions scalar when no grouping is requested."
      };
    }
    return null;
  }

  function promptMentionedColumns(metadata, question) {
    metadata = metadata || {};
    var headings = metadata.headings || [];
    var qKey = columnKey(question);
    var tokens = String(question || "").toLowerCase().match(/[a-z0-9_]+/g) || [];
    return headings.filter(function (heading) {
      var key = columnKey(heading);
      if (key && key.length <= 3) return tokens.indexOf(key) >= 0;
      return key && key.length >= 3 && qKey.indexOf(key) >= 0;
    }).sort(function (a, b) { return columnKey(b).length - columnKey(a).length; });
  }

  function suggestionIntentSignature(metadata, question) {
    var q = String(question || "");
    var forbidden = forbiddenColumns(metadata, q).map(columnKey);
    var mentioned = promptMentionedColumns(metadata, q).filter(function (column) {
      return forbidden.indexOf(columnKey(column)) < 0;
    });
    return {
      terms: suggestionRequiredIntentTerms(q),
      chartType: requestedChartType(q),
      asksTime: asksForTimeBreakdown(q),
      mentionedColumns: mentioned.map(columnKey),
      numericColumns: mentionedNumericColumns(metadata, q).map(columnKey),
      forbiddenColumns: forbidden,
      pivot: pivotMatrixIntent(metadata, q),
      bucket: nonBucketComparisonIntent(metadata, q)
    };
  }

  function validateSuggestionPreservesOriginalIntent(metadata, originalQuestion, suggestedPrompt) {
    var original = String(originalQuestion || "");
    var suggested = String(suggestedPrompt || "");
    if (!original || !suggested) return { ok: true, issues: [] };
    var o = suggestionIntentSignature(metadata, original);
    var s = suggestionIntentSignature(metadata, suggested);
    var issues = [];
    var sTerms = {};
    s.terms.forEach(function (term) { sTerms[term] = true; });
    o.terms.forEach(function (term) {
      if (!sTerms[term]) {
        issues.push("Suggestion dropped required intent term `" + term.replace(/_/g, " ") + "` from the original prompt.");
      }
    });
    if ((o.terms.indexOf("per_unit") >= 0 || o.terms.indexOf("aov") >= 0) && !/\b(sum\s+`?revenue`?\s*\/|revenue\s*\/|per\s+unit|average\s+revenue\s+per\s+unit|aov|average\s+order\s+value)\b/i.test(suggested)) {
      issues.push("Suggestion changed a ratio/average prompt into a plain total metric.");
    }
    if (o.terms.indexOf("per_unit") >= 0 && !/\b(units?\s+sold|quantity|qty|sum\s+`?[^`]*units?[^`]*`?|sum\s+`?[^`]*quantity[^`]*`?)\b/i.test(suggested)) {
      issues.push("Suggestion dropped the unit denominator required for Revenue per Unit.");
    }
    if (o.chartType && s.chartType && o.chartType !== s.chartType) {
      issues.push("Suggestion changed requested chart type from `" + o.chartType + "` to `" + s.chartType + "`.");
    }
    if (o.asksTime && !s.asksTime) {
      issues.push("Suggestion dropped the requested time/month/quarter grouping.");
    }
    if (o.pivot && !s.pivot) {
      issues.push("Suggestion dropped matrix/pivot rows-columns-values structure.");
    }
    if (o.bucket && (!s.bucket || columnKey(o.bucket.value) !== columnKey(s.bucket.value))) {
      issues.push("Suggestion dropped the requested value-vs-non-value comparison.");
    }
    if (!o.asksTime && !promptRequestsGrouping(original) && promptRequestsGrouping(suggested)) {
      issues.push("Suggestion added grouping/breakdown even though the original prompt is scalar.");
    }
    if (!o.asksTime && !promptRequestsGrouping(original) && /\b(share|contribution|percentage\s+of\s+total|percent\s+of\s+total)\b/i.test(suggested) && !/\b(share|contribution|percentage\s+of\s+total|percent\s+of\s+total)\b/i.test(original)) {
      issues.push("Suggestion changed a scalar prompt into a contribution/share prompt.");
    }
    o.mentionedColumns.forEach(function (key) {
      if (s.mentionedColumns.indexOf(key) < 0) {
        issues.push("Suggestion dropped required column `" + key + "` from the original prompt.");
      }
    });
    if (!o.asksTime && !promptRequestsGrouping(original)) {
      s.mentionedColumns.forEach(function (key) {
        if (o.mentionedColumns.indexOf(key) < 0) {
          issues.push("Suggestion added an unrequested column `" + key + "` to a scalar prompt.");
        }
      });
    }
    o.numericColumns.forEach(function (key) {
      if (s.numericColumns.indexOf(key) < 0 && suggested.toLowerCase().indexOf(key.toLowerCase()) < 0) {
        issues.push("Suggestion dropped required metric `" + key + "` from the original prompt.");
      }
    });
    if (o.numericColumns.length) {
      s.numericColumns.forEach(function (key) {
        if (o.numericColumns.indexOf(key) < 0) {
          issues.push("Suggestion added a different metric `" + key + "` that was not requested.");
        }
      });
    }
    o.forbiddenColumns.forEach(function (key) {
      if (s.mentionedColumns.indexOf(key) >= 0 && !new RegExp("\\b(not|do\\s+not|without|exclude|ignore)\\b[^.]{0,80}" + key, "i").test(columnKey(suggested))) {
        issues.push("Suggestion reintroduced forbidden column `" + key + "`.");
      }
    });
    return { ok: !issues.length, issues: issues };
  }

  function validatePromptSuggestion(metadata, suggestion, originalQuestion) {
    var preservation = validateSuggestionPreservesOriginalIntent(metadata, originalQuestion, suggestion && suggestion.prompt);
    if (!preservation.ok) return { ok: false, issues: preservation.issues, suggestion: suggestion };
    var availability = suggestionPromptAvailability(metadata, suggestion && suggestion.prompt);
    if (!availability.ok) return { ok: false, issues: availability.issues, suggestion: suggestion };
    try {
      var plan = buildAnalyticsExecutionPlan(metadata, suggestion.prompt);
      var confidence = plan.confidence || analyticsConfidenceScore(metadata, suggestion.prompt, plan);
      if (confidence.score < 60) return { ok: false, issues: confidence.reasons || ["Suggestion confidence is too low."], suggestion: suggestion };
      suggestion.executionPlan = plan;
      suggestion.validation = { ok: true, issues: [], preflight: true };
      var isPivotSuggestion = plan.intentType === "pivot_matrix" || plan.deterministicRoute === "pivotMatrixBlocks";
      suggestion.resultSnapshot = {
        tables: isPivotSuggestion ? 1 : 0,
        charts: isPivotSuggestion ? (plan.chartType ? 1 : 0) : (asksForGraph(suggestion.prompt) ? 1 : 0),
        preflight: true
      };
      suggestion.deterministicRoute = plan.deterministicRoute || plan.intentType || "manual_deterministic";
      return { ok: true, issues: [], suggestion: suggestion };
    } catch (error) {
      return { ok: false, issues: [error && error.message || "Suggestion validation failed."], suggestion: suggestion };
    }
  }

  function analyticsSelfRepairSuggestions(metadata, question) {
    metadata = metadata || {};
    var q = String(question || "").replace(/\s+/g, " ").trim();
    var repairs = [];
    function add(title, prompt, reason, route) {
      prompt = String(prompt || "").replace(/\s+/g, " ").trim();
      if (!prompt) return;
      repairs.push({ title: title, prompt: prompt, reason: reason || "Self-repair candidate", deterministicRoute: route || "" });
    }
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "sale date", "admission date", "month"]);
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    var quantity = quantityColumn(metadata);
    var pivot = pivotMatrixIntent(metadata, q);
    if (pivot) {
      add("Self-repaired matrix prompt",
        "Create a matrix table where rows are " + columnLabel(pivot.rowColumn === "__month__" ? "Month" : (pivot.rowColumns && pivot.rowColumns.length ? pivot.rowColumns.join(" + ") : pivot.rowColumn)) + ", columns are " + columnLabel(pivot.columnColumn) + ", and values are " + (pivot.valueMode === "count" ? "encounter/order counts" : "SUM `" + pivot.valueColumn + "`") + ". Include row totals and column totals. Do not collapse this into one grouped summary.",
        "Auto-repair keeps matrix rows, columns, values, and totals intact.",
        "pivotMatrixBlocks");
    }
    var combo = combinationRankingIntent(metadata, q);
    if (combo) {
      var measures = requestedNumericMeasures(metadata, q).filter(function (measure) {
        return combo.columns.indexOf(measure.column) === -1;
      });
      var include = ["COUNT rows/encounters/orders"];
      if (revenue && !measures.some(function (measure) { return measure.column === revenue && measure.mode === "sum"; })) include.push("SUM `" + revenue + "`");
      if (profit && /\bprofit|margin\b/i.test(q)) include.push("SUM `" + profit + "`");
      if (quantity && /\bunits?|quantity|per\s+unit\b/i.test(q)) include.push("SUM `" + quantity + "`");
      measures.forEach(function (measure) {
        var formula = measureFormula(measure);
        if (formula && include.indexOf(formula) === -1) include.push(formula);
      });
      if (/\baverage\s+revenue\s+per\s+unit|revenue\s+per\s+unit|per\s+unit\b/i.test(q) && revenue && quantity) {
        include.push("Average Revenue per Unit = SUM `" + revenue + "` / SUM `" + quantity + "`");
      }
      var sortFormula = (measures.filter(function (measure) {
        return measure.mode === "sum" && (measure.column === revenue || /\btotal|bill|revenue|sales\b/i.test(measure.column));
      })[0] || measures.filter(function (measure) { return measure.mode === "sum"; })[0]);
      var sortMetric = sortFormula ? measureFormula(sortFormula) : combo.sortMetric;
      add("Self-repaired combination ranking prompt",
        "Create a top " + combo.limit + " grouped combination table. Group by `" + combo.columns.join("` + `") + "`. Include " + include.join("; ") + ". Sort by " + sortMetric + " " + (combo.direction === "asc" ? "ascending" : "descending") + ". This is a grouped combination ranking, not a row-level outlier list.",
        "Auto-repair prevents top-N combination prompts from being routed as row-level outliers.",
        "genericCombinationBlocks");
    }
    if (/\breadmission\s+rate\b/i.test(q) && /\bsatisfaction\b/i.test(q)) {
      var department = findColumnByWords(metadata, ["department", "dept", "service line"]) || resolveGroupColumn(metadata, q);
      var readmit = findColumnByWords(metadata, ["readmission", "readmitted", "readmissionrate", "readmission rate", "readmission30days", "readmitted30days", "isreadmitted"]);
      var metricParts = [];
      if (readmit) metricParts.push("Readmission Rate % = readmitted rows / encounter count x 100 using `" + readmit + "`");
      metricParts.push("COUNT rows/encounters");
      requestedNumericMeasures(metadata, q).forEach(function (measure) {
        var formula = measureFormula(measure);
        if (formula && metricParts.indexOf(formula) === -1) metricParts.push(formula);
      });
      if (department && metricParts.length > 2) {
        add("Self-repaired healthcare performance prompt",
          "Create a department performance table. Group by `" + department + "`. Include " + metricParts.join("; ") + ". Highlight departments where readmission rate is high and average satisfaction score is low. Do not group by the readmission flag itself.",
          "Auto-repair keeps readmission as a rate metric and preserves requested averages.",
          "genericMultiMetricGroupBlocks");
      }
    }
    if (/\bkpi|dashboard\b/i.test(q)) {
      var kpiParts = [];
      if (/\btotal\s+(bill|revenue|sales)\b/i.test(q) && revenue) kpiParts.push("Total Revenue/Bill = SUM `" + revenue + "`");
      if (/\btotal\s+profit\b/i.test(q) && profit) kpiParts.push("Total Profit = SUM `" + profit + "`");
      if (/\btotal\s+(units?\s+sold|quantity)\b/i.test(q) && quantity) kpiParts.push("Total Units Sold = SUM `" + quantity + "`");
      if (/\baverage\s+revenue\s+per\s+unit|revenue\s+per\s+unit\b/i.test(q) && revenue && quantity) kpiParts.push("Average Revenue per Unit = SUM `" + revenue + "` / SUM `" + quantity + "`");
      if (kpiParts.length) {
        add("Self-repaired KPI formula prompt",
          "Create a KPI dashboard using all parsed rows. Use these exact KPI formulas: " + kpiParts.join("; ") + ". For any Top KPI, rank the requested dimension by SUM `" + (revenue || profit || quantity || "value") + "`. Include a KPI table with Value and Formula before the cards.",
          "Auto-repair makes KPI formulas auditable before cards render.",
          "kpiDashboardBlocks");
      }
    }
    if (asksForTimeBreakdown(q) && dateColumn) {
      var group = /\bcustomer\s+type\b/i.test(q) ? customerTypeColumn(metadata) :
        (/\badmission\s*type\b/i.test(q) ? findColumnByWords(metadata, ["admission type", "admissiontype"]) :
          (/\bdepartment\b/i.test(q) ? findColumnByWords(metadata, ["department"]) : resolveGroupColumn(metadata, q)));
      var metrics = mentionedNumericColumns(metadata, q).filter(function (column) { return column !== group; });
      if (metrics.length && group && !/\bmatrix|pivot\b/i.test(q)) {
        add("Self-repaired time-series prompt",
          "Create a month-level comparison table and chart. Group rows by month from `" + dateColumn + "`" + (group ? " and by `" + group + "`" : "") + ". Use " + metrics.map(function (column) { return "SUM `" + column + "`"; }).join("; ") + ". Keep requested metrics and series separate, and do not group by identifier columns.",
          "Auto-repair preserves month-level grouping and requested metrics.",
          "genericTimeSeriesByGroupBlocks");
      }
    }
    if (/\b(outlier|unusually|anomal)\b/i.test(q)) {
      add("Self-repaired row-level outlier prompt",
        q + " Return a row-level table only because the prompt explicitly asks for outliers/anomalies. Include every column named in the prompt and add an outlier score formula. Do not replace this with a category summary.",
        "Auto-repair preserves true row-level outlier intent.",
        "genericOutlierBlocks");
    }
    return repairs;
  }

  function analyticsPromptSuggestions(metadata, question) {
    metadata = metadata || {};
    var q = String(question || "").replace(/\s+/g, " ").trim();
    if (q.length < 4) return [];
    if (manualNarrativeInterpretationIntent(q)) return [];
    if (manualPromptNeedsDatasetGuidance(q) || !manualPromptLooksDatasetRelated(metadata, q)) return [];
    if (explicitManualColumnIssue(metadata, q)) return [];
    var scalarFullDataInstruction = "Use all parsed rows, state the exact scalar formula, validate the value before inserting the answer, and do not group unless a grouping column is explicitly requested.";
    var groupedFullDataInstruction = "Use all parsed rows, state the exact grouping and formula, validate table and chart consistency before inserting the answer.";
    var correlationFullDataInstruction = "Use all parsed rows where both values are numeric, state the Pearson correlation formula, return the correlation value and comparable row count, and do not group unless a grouping column is explicitly requested.";
    var metadataFullDataInstruction = "Use parsed workbook metadata only, return the requested row/column counts or missing-value table, and do not group or create a chart unless explicitly requested.";
    function compactInstructionText(text) {
      return String(text || "").toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
    }
    function hasFullDataCheckedInstruction(text) {
      var normalized = compactInstructionText(text);
      return normalized.indexOf(compactInstructionText(scalarFullDataInstruction)) >= 0 ||
        normalized.indexOf(compactInstructionText(groupedFullDataInstruction)) >= 0 ||
        normalized.indexOf(compactInstructionText(correlationFullDataInstruction)) >= 0 ||
        normalized.indexOf(compactInstructionText(metadataFullDataInstruction)) >= 0;
    }
    var currentKey = promptIntentKey(q);
    var suggestions = [];
    function add(title, prompt, reason) {
      prompt = String(prompt || "").replace(/\s+/g, " ").trim();
      if (!prompt) return;
      var promptKey = promptIntentKey(prompt);
      if (promptKey === currentKey) return;
      if (suggestions.some(function (item) { return promptIntentKey(item.prompt) === promptKey; })) return;
      suggestions.push({ title: title, prompt: prompt, reason: reason || "Validated wording" });
    }
    var metadataIntent = isMetadataManualQuestion(q, metadata);
    var groupedMissingColumn = groupedMissingValuesColumn(metadata, q);
    if (groupedMissingColumn) {
      add("Grouped missing-values prompt",
        "Count missing values column-wise grouped by `" + groupedMissingColumn + "`. Use all parsed rows, include one row per `" + groupedMissingColumn + "` value, show row count and missing counts for columns with missing values, and summarize all-zero columns.",
        "Preserves the requested grouped missing-values intent.");
      return suggestions;
    }
    var scalarIntent = scalarAnalyticsIntent(metadata, q);
    if (scalarIntent) {
      add(scalarIntent.title, scalarIntent.prompt, scalarIntent.reason);
      return suggestions;
    }
    analyticsSelfRepairSuggestions(metadata, q).forEach(function (repair) {
      var repairPrompt = String(repair && repair.prompt || "").replace(/\s+/g, " ").trim();
      var repairKey = promptIntentKey(repairPrompt);
      if (repairKey && repairKey !== currentKey && !suggestions.some(function (item) { return promptIntentKey(item.prompt) === repairKey; })) {
        repair.prompt = repairPrompt;
        suggestions.push(repair);
      }
    });
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    var discount = discountColumn(metadata);
    var quantity = quantityColumn(metadata);
    var payment = paymentColumn(metadata);
    var category = categoryColumn(metadata);
    var city = cityColumn(metadata);
    var region = regionColumn(metadata);
    var customerType = customerTypeColumn(metadata);
    var drug = drugColumn(metadata);
    var manufacturer = manufacturerColumn(metadata);
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "sale date", "month"]);
    var bucketIntent = nonBucketComparisonIntent(metadata, q);
    var metric = /\bprofit\b/i.test(q) && profit ? profit : /\bdiscount\b/i.test(q) && discount ? discount : /\bunits?\s+sold|quantity|qty\b/i.test(q) && quantity ? quantity : revenue;
    var group = /\bpayment\s*method\b/i.test(q) ? payment :
      /\bcustomer\s+type|customer\s+segment|segment\b/i.test(q) ? customerType :
      /\bdrug\s+name|\bdrug\b/i.test(q) ? drug :
      /\bmanufacturer\b/i.test(q) ? manufacturer :
      /\bregion\b/i.test(q) ? region :
      /\bcity\b/i.test(q) ? city :
      /\bcategory|product\s+category\b/i.test(q) ? category :
      resolveGroupColumn(metadata, q);
    var countIntent = /\b(count|counts|frequency|number of|how many|encounter count|count encounters|order count|count orders|patient outcomes?|values?)\b/i.test(q) &&
      !/\b(total|sum|average|avg|mean|median|min(?:imum)?|max(?:imum)?|billing|bill|cost|revenue|profit|claim amount)\b/i.test(q);
    var pivot = pivotMatrixIntent(metadata, q);
    if (pivot) {
      add("Matrix / pivot-safe prompt",
        "Create a matrix table where rows are " + columnLabel(pivot.rowColumn === "__month__" ? "Month" : (pivot.rowColumns && pivot.rowColumns.length ? pivot.rowColumns.join(" + ") : pivot.rowColumn)) + ", columns are " + columnLabel(pivot.columnColumn) + ", and values are " + (pivot.valueMode === "count" ? "order counts" : "total " + columnLabel(pivot.valueColumn)) + ". Keep rows and columns exactly as requested and include row totals and column totals.",
        "Preserves rows/columns/values instead of collapsing to one group.");
    } else if (/\bmatrix|pivot|heatmap|rows?\s+are|columns?\s+are|rows?\s+should\s+be|columns?\s+should\s+be\b/i.test(q)) {
      add("Matrix wording",
        "Create a matrix table using the requested row dimension, column dimension, and value metric from my prompt. Do not collapse it to one grouped summary. Include totals and explain the aggregation formula.",
        "Forces pivot/matrix output.");
    }
    if (/\bkpi|dashboard\b/i.test(q)) {
      var kpis = [];
      if (/\btotal\s+revenue\b/i.test(q) && revenue) kpis.push("Total Revenue = SUM `" + revenue + "`");
      if (/\btotal\s+profit\b/i.test(q) && profit) kpis.push("Total Profit = SUM `" + profit + "`");
      if (/\btotal\s+discount\b/i.test(q) && discount) kpis.push("Total Discount = SUM `" + discount + "`");
      if (/\btotal\s+(units?\s+sold|quantity|qty)\b/i.test(q) && quantity) kpis.push("Total Units Sold = SUM `" + quantity + "`");
      if (/\baverage\s+discount\s*%|discount\s*%\b/i.test(q) && discount && revenue) kpis.push("Average Discount % = SUM `" + discount + "` / SUM `" + revenue + "` x 100");
      if (/\bprofit\s+margin\s*%|margin\s*%\b/i.test(q) && profit && revenue) kpis.push("Profit Margin % = SUM `" + profit + "` / SUM `" + revenue + "` x 100");
      if (/\baverage\s+revenue\s+per\s+unit|revenue\s+per\s+unit\b/i.test(q) && revenue && quantity) kpis.push("Average Revenue per Unit = SUM `" + revenue + "` / SUM `" + quantity + "`");
      if (/\btop\s+drug|top\s+product\b/i.test(q) && drug && revenue) kpis.push("Top " + columnLabel(drug) + " by SUM `" + revenue + "`");
      if (/\btop\s+region|top\s+city\b/i.test(q) && region && revenue) kpis.push("Top " + columnLabel(region) + " by SUM `" + revenue + "`");
      if (/\btop\s+customer\s+type|top\s+segment\b/i.test(q) && customerType && revenue) kpis.push("Top " + columnLabel(customerType) + " by SUM `" + revenue + "`");
      add("KPI-safe prompt",
        "Create a KPI dashboard with these exact KPIs: " + (kpis.length ? kpis.join("; ") : "the KPIs named in my prompt with exact formulas") + ". Include a KPI table with Value and Formula, then responsive KPI cards.",
        "Prevents missing KPI values and missing formulas.");
    }
    if (/\b(payment\s*method|paymentmethod)\b/i.test(q) && /\b(order\s+count|count\s+orders|orders?|share|distribution|mix)\b/i.test(q) && payment && !/\bby\s+month|each\s+month|monthly|date\s+wise\b/i.test(q)) {
      add("Grouped count prompt",
        "Create a comparison chart of " + columnLabel(payment) + " order count. Group by `" + payment + "` only, not by Date, Customer, or OrderID. Sort highest count to lowest. Include Count and Percent, and show the most used and least used " + columnLabel(payment) + ".",
        "Avoids OrderID sum and accidental date/customer grouping.");
    }
    if (countIntent && group && !pivot) {
      var countLabel = /\bencounter|visit|admission|patient\b/i.test(q) ? "encounter" : /\border|transaction\b/i.test(q) ? "order" : "row";
      add("Validated count prompt",
        "Count " + countLabel + "s by " + columnLabel(group) + ". Group by `" + group + "` only, use all parsed rows" + (/\bblank|blanks|missing|null\b/i.test(q) ? ", include blank/missing values as a visible category" : "") + ", include Count and Percent, sort highest count to lowest, and add a readable chart.",
        "Keeps this as a grouped count instead of changing it to billing or another metric.");
    }
    if (bucketIntent && bucketIntent.column && asksForTimeBreakdown(q) && dateColumn) {
      var metricParts = [];
      var formulaParts = [];
      if (/\brevenue\b|\bsales\b/i.test(q) && revenue) { metricParts.push(columnLabel(revenue)); formulaParts.push("SUM `" + revenue + "`"); }
      if (/\bprofit\b/i.test(q) && profit) { metricParts.push(columnLabel(profit)); formulaParts.push("SUM `" + profit + "`"); }
      if (/\bunits?\s+sold\b|\bquantity\b|\bqty\b/i.test(q) && quantity) { metricParts.push(columnLabel(quantity)); formulaParts.push("SUM `" + quantity + "`"); }
      if (!metricParts.length && revenue) { metricParts.push(columnLabel(revenue)); formulaParts.push("SUM `" + revenue + "`"); }
      add("Bucket comparison prompt",
        "Compare " + bucketIntent.value + " vs non-" + bucketIntent.value + " by month using " + metricParts.join(" and ") + ". Group rows by month from `" + dateColumn + "` and by whether `" + bucketIntent.column + "` equals `" + bucketIntent.value + "`. Use " + formulaParts.join("; ") + ". Keep " + bucketIntent.value + " and non-" + bucketIntent.value + " as separate chart series and do not group by Drug Name, Customer, OrderID, or individual dates.",
        "Preserves the requested value-vs-non-value comparison.");
    }
    if (/\b(revenue\s+per\s+unit|average\s+order\s+value|aov|per\s+unit)\b/i.test(q) && revenue && quantity) {
      if (asksForTimeBreakdown(q) && group && dateColumn) {
        add("Ratio time-series prompt",
          "Create a monthly line chart and table of Average Revenue per Unit by " + columnLabel(group) + ". Group rows by month from `" + dateColumn + "` and by `" + group + "`. Use Average Revenue per Unit = SUM `" + revenue + "` / SUM `" + quantity + "`. Include `" + revenue + "`, `" + quantity + "`, and Average Revenue per Unit. Keep `" + group + "` as separate chart series and do not group by identifier columns.",
          "Preserves the requested average/per-unit formula.");
      }
      add("Ratio formula prompt",
        q + " Use exact formula: Average Revenue per Unit = SUM `" + revenue + "` / SUM `" + quantity + "` for each requested group. Include `" + revenue + "`, `" + quantity + "`, and the calculated ratio.",
        "Makes the formula explicit.");
    }
    var timeSeriesGroupIsCalendar = /^(year|month|quarter|week)$/i.test(String(group || ""));
    if (!bucketIntent && asksForTimeBreakdown(q) && metric && dateColumn && (!promptRequestsGrouping(q) || timeSeriesGroupIsCalendar)) {
      var timePeriod = /\byear|yearly|annual|year[-\s]*wise\b/i.test(q) || columnKey(q).indexOf("yearwise") >= 0 ? "year" : /\bquarter|quarterly|quarter[-\s]*wise\b/i.test(q) ? "quarter" : "month";
      add("Time-series total prompt",
        "Create a " + timePeriod + "ly table and readable chart of total " + columnLabel(metric) + ". Group rows by " + timePeriod + " from `" + dateColumn + "` only. Use SUM `" + metric + "` and do not add any category grouping.",
        "Keeps time-series totals at the requested period only.");
    }
    if (!bucketIntent && asksForTimeBreakdown(q) && group && metric && dateColumn && promptRequestsGrouping(q) && !timeSeriesGroupIsCalendar) {
      var period = /\byear|yearly|annual|year[-\s]*wise\b/i.test(q) || columnKey(q).indexOf("yearwise") >= 0 ? "year" : /\bquarter|quarterly|too\s+many\s+months|summari[sz]e\s+by\s+quarter\b/i.test(q) ? "quarter" : "month";
      add("Time-series prompt",
        "Create a " + period + "ly comparison chart of total " + columnLabel(metric) + " by " + columnLabel(group) + ". Group rows by " + period + " from `" + dateColumn + "` and by `" + group + "`. Use SUM `" + metric + "`. Keep `" + group + "` as separate chart series and do not group by identifier columns.",
        "Keeps time period plus requested series.");
    }
    if (/\bprofit\s+margin|discount\s*%|contribution|share\b/i.test(q) && revenue) {
      var formulas = [];
      if (discount && /\bdiscount\b/i.test(q)) formulas.push("Discount % = SUM `" + discount + "` / SUM `" + revenue + "` x 100");
      if (profit && /\bprofit\s+margin|margin\b/i.test(q)) formulas.push("Profit Margin % = SUM `" + profit + "` / SUM `" + revenue + "` x 100");
      if (profit && /\bprofit\s+(share|contribution)\b/i.test(q)) formulas.push("Profit Contribution % = group SUM `" + profit + "` / total SUM `" + profit + "` x 100");
      if (/\brevenue\s+(share|contribution)\b/i.test(q)) formulas.push("Revenue Contribution % = group SUM `" + revenue + "` / total SUM `" + revenue + "` x 100");
      if (formulas.length) add("Formula-safe prompt", q + " Use exact formulas: " + formulas.join("; ") + ". Include all formula inputs in the table and validate the chart against the table.", "Prevents formula mixups.");
    }
    if (/\b(outlier|unusually|anomal|risky)\b/i.test(q)) {
      add("Row-level outlier prompt",
        q + " Return a row-level or requested-grain table, not a category summary. Include the columns named in the prompt and sort by the requested risk/outlier score.",
        "Keeps outlier questions from collapsing to category totals.");
    }
    if (!suggestions.length && group && metric && !countIntent && promptRequestsGrouping(q)) {
      add("Validated grouped prompt",
        "Show total " + columnLabel(metric) + " by " + columnLabel(group) + ". Group by `" + group + "` only, use all parsed rows, include a table with values and percent contribution, and add a readable chart.",
        "General safe analytics wording.");
    }
    if (!hasFullDataCheckedInstruction(q) && (suggestions.length || scalarIntent || metadataIntent || promptRequestsGrouping(q) || /\b(total|totl|tatal|sum|average|avg|avrage|averge|mean|median|min|max|count|rate|percentage|percent|correlation|iqr|verify|mismatch|unique|distinct|duplicates?|missing)\b/i.test(q))) {
      var fullDataInstruction = metadataIntent ? metadataFullDataInstruction : (/\b(correlation|correlate|pearson)\b/i.test(q) ? correlationFullDataInstruction : (scalarIntent ? scalarFullDataInstruction : groupedFullDataInstruction));
      add("Full-data checked prompt", q + " " + fullDataInstruction, "Adds validation and full-row scope.");
    }
    var validated = [];
    suggestions.some(function (suggestion) {
      var checked = validatePromptSuggestion(metadata, suggestion, q);
      if (checked.ok) validated.push(checked.suggestion);
      return validated.length >= 4;
    });
    return validated.map(function (suggestion, index) {
      suggestion.tone = promptSuggestionTone(q, index);
      suggestion.estimate = estimateManualPromptCredits(metadata, suggestion.prompt);
      return suggestion;
    });
  }

  function parseValueMappings(question) {
    var map = {};
    var text = String(question || "");
    text.replace(/\b([^=,()]+?)\s*=\s*([^,()]+)/g, function (_, rawKey, rawValue) {
      var key = String(rawKey || "").trim();
      var value = String(rawValue || "").trim();
      if (key && value && key.length <= 20) map[columnKey(key)] = value;
      return _;
    });
    return map;
  }

  function resolveQuestionColumn(metadata, question, fallbackKind) {
    var headings = metadata.headings || [];
    var q = columnKey(question);
    var blocked = forbiddenColumns(metadata, question).map(columnKey);
    function allowed(heading) {
      return blocked.indexOf(columnKey(heading)) < 0;
    }
    var searchable = headings.filter(allowed);
    var exactWords = headings.filter(function (heading) {
      var key = columnKey(heading);
      return key && allowed(heading) && q.indexOf(key) >= 0;
    });
    if (asksForDistribution(question) && exactWords.length > 1) {
      var categoricalExact = exactWords.filter(function (heading) {
        var key = columnKey(heading);
        return !/^(date|month|order|orderid|id|row)$/.test(key) && !/(date|orderid)$/.test(key);
      });
      if (categoricalExact.length) exactWords = categoricalExact;
    }
    if (exactWords.length) return exactWords.sort(function (a, b) { return columnKey(b).length - columnKey(a).length; })[0];
    var preferred = ["paymentmethod", "payment", "method", "supplier", "vendor", "department", "category", "zone", "city", "country", "region", "state", "status", "type", "segment", "class", "group", "month", "date"];
    for (var i = 0; i < preferred.length; i += 1) {
      if (q.indexOf(preferred[i]) >= 0) {
        var match = searchable.filter(function (heading) { return columnKey(heading).indexOf(preferred[i]) >= 0; })[0];
        if (match) return match;
      }
    }
    if (/\b(requested|from\s+my\s+prompt|as\s+requested)\b/i.test(String(question || ""))) return "";
    var fallback = resolveColumn(metadata, "", fallbackKind || "category");
    return isForbiddenColumn(metadata, question, fallback) ? (searchable[0] || "") : fallback;
  }

  function resolveMetricColumn(metadata, question) {
    var q = columnKey(question);
    var numeric = Object.keys(metadata.numericStats || {});
    var headings = metadata.headings || [];
    var exact = numeric.filter(function (heading) {
      var key = columnKey(heading);
      return key && q.indexOf(key) >= 0;
    }).sort(function (a, b) { return columnKey(b).length - columnKey(a).length; })[0];
    if (exact) return exact;
    var preferred = [
      ["price", ["unitprice", "price", "sellingprice"]],
      ["rate", ["rate", "ratio", "score"]],
      ["stock", ["stocklevel", "stock", "inventory"]],
      ["orders", ["totalorders", "orders", "order"]],
      ["demand", ["demand", "forecast"]],
      ["time", ["time", "seconds", "duration"]],
      ["cost", ["cost", "expense"]],
      ["quantity", ["quantity", "qty", "units"]]
    ];
    for (var p = 0; p < preferred.length; p += 1) {
      if (preferred[p][1].some(function (word) { return q.indexOf(word) >= 0; })) {
        var match = numeric.filter(function (name) {
          var key = columnKey(name);
          return preferred[p][1].some(function (word) { return key.indexOf(word) >= 0; });
        })[0];
        if (match) return match;
      }
    }
    return safeDefaultMetricColumn(metadata) || numeric.filter(function (column) { return !semanticColumnInfo(metadata, column).avoidDefault; })[0] || numeric[0] || headings[0] || "";
  }

  function resolveGroupColumn(metadata, question) {
    var groupingQuestion = String(question || "")
      .replace(/\bdo\s+not\s+group\s+by\s+[^,.;!?]+/ig, " ")
      .replace(/\b(?:not|without|exclude|excluding|avoid|ignore)\s+(?:group(?:ing)?\s+)?by\s+[^,.;!?]+/ig, " ");
    var q = columnKey(groupingQuestion);
    var headings = metadata.headings || [];
    var categorical = metadata.categoricalColumns || [];
    var byMatch = groupingQuestion.match(/\b(?:by|per|based\s+on|grouped\s+by|group\s+by|for\s+each|across)\s+(.+?)(?:[,.;?]|$)/i);
    if (byMatch) {
      var byPhrase = String(byMatch[1] || "")
        .split(/,|\band\b|\bthen\b|\bsort\b|\border\b|\bhighest\b|\blowest\b|\btop\b|\bbottom\b|\bvalidate\b|\bstate\b|\buse\b/i)[0]
        .replace(/\b(?:highest|lowest|ascending|descending|to|from|all|parsed|rows?|formula|chart|table|consistency|before|inserting|answer)\b/ig, " ")
        .replace(/\s+/g, " ")
        .trim();
      var explicit = resolveLoosePromptColumn(metadata, byPhrase || byMatch[1], "category");
      if (explicit && !semanticColumnInfo(metadata, explicit).identifier && !isForbiddenColumn(metadata, question, explicit)) return explicit;
    }
    var exact = categorical.filter(function (heading) {
      var key = columnKey(heading);
      return key && q.indexOf(key) >= 0 && !isForbiddenColumn(metadata, question, heading);
    })[0];
    if (exact) return exact;
    var preferred = ["supplier", "vendor", "category", "department", "segment", "type", "class", "group", "zone", "region", "city", "state", "country", "status"];
    for (var i = 0; i < preferred.length; i += 1) {
      if (q.indexOf(preferred[i]) >= 0) {
        var match = headings.filter(function (heading) { return columnKey(heading).indexOf(preferred[i]) >= 0 && !isForbiddenColumn(metadata, question, heading); })[0];
        if (match) return match;
      }
    }
    return safeDefaultDimensionColumn(metadata) || categorical.filter(function (column) { return !semanticColumnInfo(metadata, column).avoidDefault; })[0] || "";
  }

  function dateWindowRows(metadata, rows, question) {
    var q = String(question || "");
    var match = q.match(/\blast\s+(\d+)\s*(month|months|day|days|week|weeks|year|years)\b/i);
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "created_at", "updated_at", "last_restock_date", "month"]);
    if (!match || !dateColumn) return { rows: rows, dateColumn: dateColumn, description: "" };
    var dated = rows.map(function (row) {
      var date = new Date(rowValue(row, dateColumn));
      return { row: row, date: date };
    }).filter(function (item) { return !Number.isNaN(item.date.getTime()); });
    if (!dated.length) return { rows: rows, dateColumn: dateColumn, description: "" };
    var latest = dated.reduce(function (max, item) { return item.date > max ? item.date : max; }, dated[0].date);
    var start = new Date(latest.getTime());
    var amount = Number(match[1]) || 0;
    var unit = match[2].toLowerCase();
    if (unit.indexOf("month") === 0) start.setMonth(start.getMonth() - amount);
    else if (unit.indexOf("week") === 0) start.setDate(start.getDate() - amount * 7);
    else if (unit.indexOf("year") === 0) start.setFullYear(start.getFullYear() - amount);
    else start.setDate(start.getDate() - amount);
    var filtered = dated.filter(function (item) { return item.date > start && item.date <= latest; }).map(function (item) { return item.row; });
    return {
      rows: filtered.length ? filtered : rows,
      dateColumn: dateColumn,
      description: "Filtered to the last " + amount + " " + unit + " using `" + dateColumn + "` from " + start.toLocaleDateString("en") + " to " + latest.toLocaleDateString("en") + "."
    };
  }

  function findColumnByWords(metadata, words) {
    var headings = metadata.headings || [];
    for (var i = 0; i < words.length; i += 1) {
      var wanted = columnKey(words[i]);
      var exact = headings.filter(function (heading) { return columnKey(heading) === wanted; })[0];
      if (exact) return exact;
    }
    for (var w = 0; w < words.length; w += 1) {
      var word = columnKey(words[w]);
      var partial = headings.filter(function (heading) {
        var key = columnKey(heading);
        return key.indexOf(word) >= 0 || word.indexOf(key) >= 0;
      }).sort(function (a, b) { return columnKey(b).length - columnKey(a).length; })[0];
      if (partial) return partial;
    }
    return "";
  }

  function resolvePromptColumnPhrase(metadata, phrase, fallbackKind) {
    metadata = metadata || {};
    phrase = String(phrase || "")
      .replace(/[`'"]/g, "")
      .replace(/\b(?:the|a|an|total|sum|count|counts|order|orders|rows?|values?|value|field|column|columns|metric|metrics|should|be|as|by|of|and|with|include|show)\b/ig, " ")
      .replace(/\s+/g, " ")
      .trim();
    if (!phrase) return "";
    var headings = metadata.headings || [];
    var phraseKey = columnKey(phrase);
    var exact = headings.filter(function (heading) { return columnKey(heading) === phraseKey; })[0];
    if (exact) return exact;
    var partial = headings.filter(function (heading) {
      var key = columnKey(heading);
      return key && (phraseKey.indexOf(key) >= 0 || key.indexOf(phraseKey) >= 0);
    }).sort(function (a, b) { return columnKey(b).length - columnKey(a).length; })[0];
    if (partial) return partial;
    var tokens = phrase.split(/\s+/).filter(function (token) { return token.length > 1; });
    return findColumnByWords(metadata, [phrase].concat(tokens)) || resolveColumn(metadata, phrase, fallbackKind || "category");
  }

  function extractPromptDimension(question, label) {
    var q = String(question || "");
    var pattern = new RegExp("\\b" + label + "\\s+(?:should\\s+be|are|is|as|=)\\s+(.+?)(?=(?:\\s*,\\s*(?:and\\s+)?(?:rows?|columns?|series|values?)\\s+(?:should\\s+be|are|is|as|=))|[.;]|$)", "i");
    var match = q.match(pattern);
    return match ? match[1].replace(/\s*,\s*$/g, "").trim() : "";
  }

  function customerSegmentColumn(metadata) {
    var headings = metadata.headings || [];
    var exact = headings.filter(function (heading) {
      var key = columnKey(heading);
      return /^(customersegment|clientsegment|buyersegment|accountsegment|segment)$/.test(key);
    })[0];
    if (exact) return exact;
    return headings.filter(function (heading) {
      var key = columnKey(heading);
      return /(segment|cohort|tier|persona|audience|marketsegment)/.test(key) && !/^customer(name)?$/.test(key);
    })[0] || "";
  }

  function rowNumber(row, column) {
    return numericValue(rowValue(row, column)) || 0;
  }

  function isUnitOrComponentCostColumn(column) {
    var key = columnKey(column);
    return /(perunit|unit|perday|day|daily|handling|holding|shipping|freight|labor|labour|storage|inventory|tax|fee|component)/.test(key);
  }

  function findTotalCostColumn(metadata) {
    var headings = metadata.headings || [];
    var exact = headings.filter(function (heading) {
      return /^(totalcost|costtotal|expense|expenses|totalexpense|totalexpenses|cogs|costofgoodssold)$/i.test(columnKey(heading));
    })[0];
    if (exact) return exact;
    return headings.filter(function (heading) {
      var key = columnKey(heading);
      return /(totalcost|costtotal|totalexpense|totalexpenses|cogs|costofgoodssold)/.test(key) && !isUnitOrComponentCostColumn(heading);
    })[0] || "";
  }

  function formulaPart(column, multiplier) {
    if (!column) return "";
    return multiplier ? "`" + column + "` x `" + multiplier + "`" : "`" + column + "`";
  }

  function derivedMetricSpec(metadata, question) {
    var q = columnKey(question);
    if (!/\b(profit|loss|revenue|sales|cost|expense|margin)\b/i.test(String(question || ""))) return null;
    var profitCol = findColumnByWords(metadata, ["profit", "net_profit", "gross_profit"]);
    var revenueCol = findColumnByWords(metadata, ["revenue", "sales", "amount", "total_revenue"]);
    var discountCol = findColumnByWords(metadata, ["discount", "discount_amount", "discountvalue"]);
    var costCol = findTotalCostColumn(metadata);
    var unitPriceCol = findColumnByWords(metadata, ["unit_price", "price", "selling_price"]);
    var quantityCol = findColumnByWords(metadata, ["quantity", "qty", "units", "total_orders_last_month", "orders", "total_orders"]);
    var handlingCostCol = findColumnByWords(metadata, ["handling_cost_per_unit", "handling_cost", "cost_per_unit"]);
    var holdingCostCol = findColumnByWords(metadata, ["holding_cost_per_unit_day", "holding_cost_per_day", "holding_cost"]);
    var stockCol = findColumnByWords(metadata, ["stock_level", "stock", "inventory"]);
    function costFormula() {
      if (costCol) return "`" + costCol + "`";
      var parts = [];
      if (handlingCostCol) parts.push(quantityCol ? formulaPart(handlingCostCol, quantityCol) : "`" + handlingCostCol + "`");
      if (holdingCostCol && stockCol) parts.push("`" + holdingCostCol + "` x `" + stockCol + "` x 30 days");
      if (!parts.length && holdingCostCol) parts.push("`" + holdingCostCol + "`");
      return parts.length ? parts.join(" + ") : "0";
    }
    function revenueFormula() {
      return revenueCol ? "`" + revenueCol + "`" : "`" + (unitPriceCol || "0") + "` x `" + (quantityCol || "1") + "`";
    }
    function revenue(row) {
      if (revenueCol) return rowNumber(row, revenueCol);
      if (unitPriceCol && quantityCol) return rowNumber(row, unitPriceCol) * rowNumber(row, quantityCol);
      return 0;
    }
    function netRevenue(row) {
      return revenue(row) - (discountCol ? rowNumber(row, discountCol) : 0);
    }
    function cost(row) {
      if (costCol) return rowNumber(row, costCol);
      var total = 0;
      if (handlingCostCol && quantityCol) total += rowNumber(row, handlingCostCol) * rowNumber(row, quantityCol);
      if (holdingCostCol && stockCol) total += rowNumber(row, holdingCostCol) * rowNumber(row, stockCol) * 30;
      return total;
    }
    if (q.indexOf("margin") >= 0) {
      if (profitCol && (revenueCol || (unitPriceCol && quantityCol))) {
        return {
          name: "Profit Margin",
          formula: "Profit margin % = Profit / Revenue x 100. Profit uses `" + profitCol + "`. Revenue = " + revenueFormula() + ".",
          percentMetric: true,
          aggregateRatio: true,
          compute: function (row) {
            var rev = revenue(row);
            return rev ? (rowNumber(row, profitCol) / rev) * 100 : 0;
          }
        };
      }
      if (revenueCol || unitPriceCol || costCol || handlingCostCol || holdingCostCol) {
        return {
          name: "Profit Margin",
          formula: "Profit margin % = ((Revenue - Cost) / Revenue) x 100. Revenue = " + revenueFormula() + ". Cost = " + costFormula() + ".",
          percentMetric: true,
          compute: function (row) {
            var rev = revenue(row);
            return rev ? ((rev - cost(row)) / rev) * 100 : 0;
          }
        };
      }
    }
    if ((q.indexOf("netrevenue") >= 0 || (q.indexOf("revenue") >= 0 && q.indexOf("discount") >= 0)) && (revenueCol || (unitPriceCol && quantityCol))) {
      return {
        name: "Net Revenue after Discount",
        formula: "Net Revenue after Discount = " + (revenueCol ? "`" + revenueCol + "`" : "`" + unitPriceCol + "` x `" + quantityCol + "`") + (discountCol ? " - `" + discountCol + "`." : ". No discount column was detected, so net revenue equals revenue."),
        compute: netRevenue
      };
    }
    if (q.indexOf("revenue") >= 0 || q.indexOf("sales") >= 0) {
      if (revenueCol || (unitPriceCol && quantityCol)) {
        return {
          name: "Revenue",
          formula: revenueCol ? "Revenue uses existing column `" + revenueCol + "`." : "Revenue = `" + unitPriceCol + "` x `" + quantityCol + "`.",
          compute: revenue
        };
      }
    }
    if (profitCol) {
      return {
        name: "Profit",
        formula: "Profit uses existing column `" + profitCol + "`.",
        compute: function (row) { return rowNumber(row, profitCol); }
      };
    }
    if (q.indexOf("cost") >= 0 || q.indexOf("expense") >= 0) {
      if (costCol || handlingCostCol || holdingCostCol) {
        return {
          name: "Cost",
          formula: "Cost = " + costFormula() + ".",
          compute: cost
        };
      }
    }
    if (q.indexOf("loss") >= 0) {
      if (revenueCol || unitPriceCol || costCol || handlingCostCol || holdingCostCol) {
        return {
          name: "Loss",
          formula: "Loss = max(0, Cost - Revenue). Revenue = " + revenueFormula() + ". Cost = " + costFormula() + ".",
          compute: function (row) { return Math.max(0, cost(row) - revenue(row)); }
        };
      }
    }
    if (revenueCol || unitPriceCol || costCol || handlingCostCol || holdingCostCol) {
      return {
        name: "Profit",
        formula: "Profit = Revenue - Cost. Revenue = " + revenueFormula() + ". Cost = " + costFormula() + ".",
        compute: function (row) { return revenue(row) - cost(row); }
      };
    }
    return null;
  }

  function derivedMetricBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var spec = derivedMetricSpec(metadata, question);
    if (!metadata || !rows.length || !spec) return [];
    var q = String(question || "");
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "month", "created_at", "last_restock_date"]);
    var monthWise = /\b(month|monthly|monthwise|month-wise|month\s+wise)\b/i.test(q);
    var groupColumn = monthWise && dateColumn ? dateColumn : resolveGroupColumn(metadata, q);
    var grouped = {};
    var order = [];
    rows.forEach(function (row, index) {
      var label = monthWise ? monthLabel(rowValue(row, dateColumn)) : String(rowValue(row, groupColumn) || "Row " + (index + 1));
      if (!label) label = String(rowValue(row, groupColumn) || "Unknown");
      var dateValue = new Date(rowValue(row, dateColumn));
      var sortKey = monthWise && !Number.isNaN(dateValue.getTime()) ? dateValue.getFullYear() * 100 + dateValue.getMonth() : index;
      if (!grouped[label]) {
        grouped[label] = { total: 0, count: 0, key: sortKey };
        order.push(label);
      }
      grouped[label].total += spec.compute(row);
      grouped[label].count += 1;
    });
    order.sort(function (a, b) { return grouped[a].key - grouped[b].key; });
    order = order.slice(0, 24);
    var grandTotal = order.reduce(function (sum, label) { return sum + grouped[label].total; }, 0);
    var wantsPercent = /\b(percent|percentage|share|ratio)\b/i.test(q);
    var chartValues = order.map(function (label) {
      var value = grouped[label].total;
      if (spec.percentMetric) return chartNumber(value / Math.max(1, grouped[label].count));
      return wantsPercent && grandTotal ? chartNumber(value * 100 / grandTotal) : chartNumber(value);
    });
    var metricLabel = wantsPercent && !spec.percentMetric ? spec.name + " %" : spec.name;
    var tableRows = [["Group", spec.name, wantsPercent || spec.percentMetric ? "Percent" : "Rows"]].concat(order.map(function (label, index) {
      var value = chartNumber(grouped[label].total);
      var percent = spec.percentMetric ? chartValues[index] + "%" : (grandTotal ? chartNumber(grouped[label].total * 100 / grandTotal) + "%" : "0%");
      return [label, String(value), wantsPercent || spec.percentMetric ? percent : String(grouped[label].count)];
    }));
    var summary = "Computed " + metricLabel + " from " + rows.length + " parsed workbook rows grouped by `" + groupColumn + "`. Formula used: " + spec.formula;
    var chartType = manualChartType(q, asksForTrend(q) ? "line" : "bar");
    var chartLabels = manualChartLabelsForType(chartType, order);
    var chartCaption = manualChartCaption(chartType, order, "Generated automatically because grouped " + spec.name.toLowerCase() + " data is easier to compare in a chart. Formula: " + spec.formula);
    return [
      { type: "paragraph", data: { text: summary } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: chartType,
          title: metricLabel + " by " + (monthWise ? "Month" : groupColumn),
          labels: chartLabels.join(","),
          values: chartValues.join(","),
          xAxisLabel: chartType === "scatter" ? (monthWise ? "Month position" : groupColumn + " position") : "",
          yAxisLabel: chartType === "scatter" ? metricLabel : "",
          caption: chartCaption,
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      }
    ];
  }

  function sortedGroupBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    if (!metadata || !rows.length || !asksForSort(question)) return [];
    var q = String(question || "");
    var metric = resolveMetricColumn(metadata, q);
    var groupColumn = resolveGroupColumn(metadata, q);
    if (!metric || !groupColumn || metric === groupColumn) return [];
    var windowed = dateWindowRows(metadata, rows, q);
    var workingRows = windowed.rows;
    var ascending = !/\b(high\s+to\s+low|highest|descending|desc)\b/i.test(q);
    var grouped = {};
    var groupOrder = [];
    workingRows.forEach(function (row) {
      var group = String(rowValue(row, groupColumn) || "Unknown");
      if (!grouped[group]) {
        grouped[group] = [];
        groupOrder.push(group);
      }
      grouped[group].push(row);
    });
    groupOrder.sort(function (a, b) { return a.localeCompare(b); });
    var tableRows = [["Group", "Rank", metric, "Row label"]]; 
    var chartBlocks = [];
    groupOrder.forEach(function (group) {
      var sorted = grouped[group].slice().sort(function (a, b) {
        var diff = rowNumber(a, metric) - rowNumber(b, metric);
        return ascending ? diff : -diff;
      });
      sorted.forEach(function (row, index) {
        tableRows.push([
          group,
          String(index + 1),
          String(chartNumber(rowNumber(row, metric))),
          String(rowValue(row, "item_id") || rowValue(row, "id") || rowValue(row, "name") || rowValue(row, windowed.dateColumn) || "")
        ]);
      });
      var sample = sorted.slice(0, 20);
      var requestedType = manualChartType(q, asksForTrend(q) ? "line" : "bar");
      var chartLabels = sample.map(function (row, index) {
        return String(rowValue(row, "item_id") || rowValue(row, "id") || rowValue(row, "name") || "Item " + (index + 1));
      });
      var chartCaption = manualChartCaption(requestedType, chartLabels, "Sorted " + metric + " " + (ascending ? "ascending" : "descending") + " within `" + groupColumn + "` = " + group + ".");
      chartBlocks.push({
        type: "chart",
        data: {
          type: requestedType,
          title: metric + " " + (ascending ? "Low to High" : "High to Low") + " for " + group,
          labels: manualChartLabelsForType(requestedType, chartLabels).join(","),
          values: sample.map(function (row) { return chartNumber(rowNumber(row, metric)); }).join(","),
          xAxisLabel: requestedType === "scatter" ? "Sorted row position" : "",
          yAxisLabel: requestedType === "scatter" ? metric : "",
          caption: chartCaption,
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      });
    });
    var chartNote = chartBlocks.length > 12 ? " Charts are shown for the first 12 groups and up to 20 sorted rows per group to keep the visualization readable; the table contains all " + workingRows.length + " filtered rows." : " Charts show up to 20 sorted rows per group to keep the visualization readable; the table contains all " + workingRows.length + " filtered rows.";
    var summary = "Sorted `" + metric + "` from " + (ascending ? "low to high" : "high to low") + " within each `" + groupColumn + "` group using " + workingRows.length + " rows. Formula used: group by `" + groupColumn + "`, convert `" + metric + "` to a number, then sort each group " + (ascending ? "ascending" : "descending") + ". " + (windowed.description || "No date-window filter was requested or available.") + chartNote;
    return [
      { type: "paragraph", data: { text: summary } },
      { type: "table", data: { withHeadings: true, rows: tableRows } }
    ].concat(chartBlocks.slice(0, 12));
  }

  function aggregateRankingBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(top|bottom|highest|lowest|largest|smallest)\b/i.test(q)) return [];
    if (asksForDistribution(q)) return [];
    if (/\b(outlier|unusually|per\s+order|row-level|row\s+level)\b/i.test(q)) return [];
    var groupColumn = rankingValueGroupColumn(metadata, q) || promptGroupColumns(metadata, q, resolveGroupColumn(metadata, q))[0] || "";
    var metric = manualMetricColumn(metadata, q);
    if (!groupColumn && /\bdrug|medicine\b/i.test(q)) groupColumn = strictDrugColumn(metadata);
    else if (!groupColumn && /\bproduct|sku|item\b/i.test(q)) groupColumn = resolveNamedColumn(metadata, ["product", "productname", "item", "sku"], "category");
    if (/\brevenue\b/i.test(q)) metric = strictRevenueColumn(metadata);
    if (/\bcity\b/i.test(q)) groupColumn = cityColumn(metadata);
    if (/\bcategory\b/i.test(q)) groupColumn = categoryColumn(metadata);
    if (identifierColumns(metadata).some(function (column) { return columnKey(column) === columnKey(metric); })) return [];
    if (!groupColumn || !metric || groupColumn === metric || isForbiddenColumn(metadata, question, groupColumn)) return [];
    var mode = aggregationMode(q);
    if (mode !== "sum" && !/\b(avg|average|mean|min|max|minimum|maximum)\b/i.test(q)) mode = "sum";
    var limit = requestedLimit(q, 10);
    var bottom = rankingIsBottom(q);
    var groups = {};
    var order = [];
    rows.forEach(function (row) {
      var label = String(rowValue(row, groupColumn) || "Blank").trim() || "Blank";
      if (label === "Blank" && !/\b(blank|missing|null|empty)\b/i.test(q)) return;
      if (!groups[label]) {
        groups[label] = { rows: [] };
        order.push(label);
      }
      groups[label].rows.push(row);
    });
    order.sort(function (a, b) {
      var av = aggregateRows(groups[a].rows, metric, mode);
      var bv = aggregateRows(groups[b].rows, metric, mode);
      return (bottom ? av - bv : bv - av) || a.localeCompare(b);
    });
    order = order.slice(0, limit);
    if (!order.length) return [];
    var tableRows = [[groupColumn, metric + " (" + mode + ")", "Rows"]].concat(order.map(function (label) {
      return [label, String(chartNumber(aggregateRows(groups[label].rows, metric, mode))), String(groups[label].rows.length)];
    }));
    var chartType = manualChartType(q, "bar");
    return [
      { type: "paragraph", data: { text: "Computed " + (bottom ? "bottom" : "top") + " " + limit + " `" + groupColumn + "` groups by " + mode + " `" + metric + "` from " + rows.length + " parsed workbook rows. Formula: GROUP BY `" + groupColumn + "`, " + mode + " `" + metric + "`, sort " + (bottom ? "ascending" : "descending") + ", limit " + limit + "." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: chartType,
          title: (bottom ? "Bottom " : "Top ") + limit + " " + groupColumn + " by " + metric,
          labels: manualChartLabelsForType(chartType, order).join(","),
          values: order.map(function (label) { return chartNumber(aggregateRows(groups[label].rows, metric, mode)); }).join(","),
          xAxisLabel: chartType === "scatter" ? groupColumn + " position" : "",
          yAxisLabel: chartType === "scatter" ? metric : "",
          caption: manualChartCaption(chartType, order, "Generated from " + rows.length + " parsed workbook rows using aggregate ranking, not row-level listing."),
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      }
    ];
  }

  function monthlySegmentPerformanceBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !asksForTimeBreakdown(q)) return [];
    if (!/\b(segment|category|department|type|class)\b/i.test(q)) return [];
    if (!/\brevenue|sales|profit|order\s+count|orders\b/i.test(q)) return [];
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "month"]);
    var strictSegment = customerSegmentColumn(metadata);
    var forbidsCustomerName = /\bdo\s+not\s+group\s+by\s+customer\s+name\b|\bnot\s+by\s+customer\s+name\b|\bwithout\s+customer\s+name\b/i.test(q);
    var segmentColumn = /\bsegment\b/i.test(q) ? (strictSegment || (forbidsCustomerName ? "" : resolveGroupColumn(metadata, q))) : resolveGroupColumn(metadata, q);
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    if (!dateColumn || !segmentColumn || (!revenue && !profit)) return [];
    var orderId = orderIdentifierColumn(metadata);
    var buckets = {};
    var monthOrder = [];
    var segmentTotals = {};
    var segmentOrder = [];
    var useQuarter = /\bquarter|quarterly|too many months\b/i.test(q);
    var periodLabel = useQuarter ? "Quarter" : "Month";
    rows.forEach(function (row) {
      var month = useQuarter ? quarterBucket(rowValue(row, dateColumn)) : monthBucket(rowValue(row, dateColumn));
      if (!month) return;
      var segment = String(rowValue(row, segmentColumn) || "Blank").trim() || "Blank";
      if (!buckets[month.label]) {
        buckets[month.label] = { key: month.key, segments: {} };
        monthOrder.push(month.label);
      }
      if (!buckets[month.label].segments[segment]) buckets[month.label].segments[segment] = { revenue: 0, profit: 0, rows: 0, ids: {} };
      if (!segmentTotals[segment]) {
        segmentTotals[segment] = 0;
        segmentOrder.push(segment);
      }
      buckets[month.label].segments[segment].revenue += revenue ? rowNumber(row, revenue) : 0;
      buckets[month.label].segments[segment].profit += profit ? rowNumber(row, profit) : 0;
      buckets[month.label].segments[segment].rows += 1;
      if (orderId) buckets[month.label].segments[segment].ids[String(rowValue(row, orderId) || buckets[month.label].segments[segment].rows)] = true;
      segmentTotals[segment] += revenue ? rowNumber(row, revenue) : 1;
    });
    monthOrder.sort(function (a, b) { return buckets[a].key - buckets[b].key; });
    segmentOrder.sort(function (a, b) { return segmentTotals[b] - segmentTotals[a] || a.localeCompare(b); });
    var tableRows = [[periodLabel, segmentColumn, "Revenue", "Profit", "Order Count"]];
    monthOrder.forEach(function (month) {
      segmentOrder.forEach(function (segment) {
        var item = buckets[month].segments[segment] || { revenue: 0, profit: 0, rows: 0, ids: {} };
        var count = orderId ? Object.keys(item.ids).length : item.rows;
        tableRows.push([month, segment, String(chartNumber(item.revenue)), String(chartNumber(item.profit)), String(count)]);
      });
    });
    var chartValues = segmentOrder.map(function (segment) {
      return monthOrder.map(function (month) {
        var item = buckets[month].segments[segment] || { revenue: 0 };
        return chartNumber(item.revenue || 0);
      }).join(",");
    }).join(";");
    var requested = requestedChartType(q);
    var chartType = requested === "stackedBar" || requested === "comparison" ? requested : "stackedBar";
    return [
      { type: "paragraph", data: { text: "Computed " + periodLabel.toLowerCase() + "ly performance by `" + segmentColumn + "` from " + rows.length + " parsed workbook rows. Formula: GROUP BY " + periodLabel + " + `" + segmentColumn + "`, SUM Revenue, SUM Profit, and " + (orderId ? "COUNT_DISTINCT `" + orderId + "`" : "COUNT rows") + "." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: chartType,
          title: periodLabel + "ly Revenue by " + segmentColumn,
          labels: monthOrder.join(","),
          values: chartValues,
          seriesLabels: segmentOrder.join(","),
          caption: "Chart shows " + periodLabel.toLowerCase() + "ly Revenue by `" + segmentColumn + "` as separate series; table also includes Profit and Order Count. Generated from " + rows.length + " parsed workbook rows.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      }
    ];
  }

  function exactCountBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || derivedMetricSpec(metadata, question) || !asksForDistribution(question)) return [];
    if (/\bmatrix|pivot|rows?\s+are|columns?\s+are|row\s+dimension|column\s+dimension|requested\s+row|requested\s+column|from\s+my\s+prompt\b/i.test(q)) return [];
    if (requestedNumericMeasures(metadata, question).length || /\breadmission\s+rate|average|avg|mean|satisfaction|wait\s+time|length\s+of\s+stay\b/i.test(String(question || ""))) return [];
    var hasExplicitColumn = (metadata.headings || []).some(function (heading) {
      return promptMentionsColumn(q, heading) && !isForbiddenColumn(metadata, q, heading);
    }) || /\bby\s+[`'"]?[A-Za-z][A-Za-z0-9_ -]{1,60}\b|\b(group|grouped|split|breakdown)\s+by\b/i.test(q);
    if (!hasExplicitColumn) return [];
    var column = resolveQuestionColumn(metadata, question, "category");
    if (!column || isForbiddenColumn(metadata, question, column)) return [];
    var wantsShare = /\b(share|mix|distribution|percentage|percent)\b/i.test(String(question || ""));
    var mappings = parseValueMappings(question);
    var counts = {};
    var distinct = {};
    var order = [];
    var orderId = /\border|orders\b/i.test(String(question || "")) ? orderIdentifierColumn(metadata) : "";
    rows.forEach(function (row) {
      var raw = String(rowValue(row, column) || "").trim() || "Blank";
      var label = mappings[columnKey(raw)] || raw;
      if (!distinct[label]) {
        counts[label] = 0;
        distinct[label] = {};
        order.push(label);
      }
      if (orderId) distinct[label][String(rowValue(row, orderId) || "") || "__row_" + counts[label]] = true;
      counts[label] += orderId ? 0 : 1;
    });
    if (orderId) order.forEach(function (label) { counts[label] = Object.keys(distinct[label] || {}).length; });
    var ascending = countSortAscending(question);
    order.sort(function (a, b) { return (ascending ? counts[a] - counts[b] : counts[b] - counts[a]) || a.localeCompare(b); });
    var total = order.reduce(function (sum, label) { return sum + counts[label]; }, 0);
    var summary = "Exact " + column + " count from " + total + " workbook rows: " + order.map(function (label) {
      return label + " = " + counts[label] + (wantsShare && total ? " (" + (Math.round(counts[label] * 10000 / total) / 100) + "%)" : "");
    }).join(", ") + ".";
    var blocks = [
      { type: "paragraph", data: { text: summary } },
      { type: "table", data: { withHeadings: true, rows: [["Group", "Count", "Percent"]].concat(order.map(function (label) {
        return [label, String(counts[label]), total ? (Math.round(counts[label] * 10000 / total) / 100) + "%" : "0%"];
      })) } }
    ];
    var chartType = manualChartType(question, asksForTrend(question) ? "line" : "bar");
    var chartLabels = manualChartLabelsForType(chartType, order);
    var chartValues = order.map(function (label) {
      return wantsShare && total ? chartNumber(counts[label] * 100 / total) : counts[label];
    });
    var countFormula = orderId && !questionExplicitlyExcludesColumn(question, orderId) ? "COUNT_DISTINCT `" + orderId + "`" : "Count each order/row";
    var chartCaption = manualChartCaption(chartType, order, "Generated automatically because grouped " + (wantsShare ? "share percentages" : "counts") + " are useful to compare visually. Exact counts came from " + rows.length + " parsed workbook rows. Formula: " + countFormula + " by `" + column + "`; Percent = group count / " + total + " total counted orders x 100.");
    blocks.push({
      type: "chart",
      data: {
        type: chartType,
        title: column + (wantsShare ? " Share %" : " Count"),
        labels: chartLabels.join(","),
        values: chartValues.join(","),
        xAxisLabel: chartType === "scatter" ? column + " position" : "",
        yAxisLabel: chartType === "scatter" ? (wantsShare ? "Share %" : "Count") : "",
        caption: chartCaption,
        color: "#2563eb",
        colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
      }
    });
    return blocks;
  }

  function monthBucket(value) {
    var date = new Date(value);
    if (Number.isNaN(date.getTime())) return null;
    var year = date.getFullYear();
    var month = String(date.getMonth() + 1).padStart(2, "0");
    return {
      key: year * 100 + date.getMonth(),
      label: year + "-" + month
    };
  }

  function yearBucket(value) {
    var date = new Date(value);
    if (Number.isNaN(date.getTime())) return null;
    var year = date.getFullYear();
    return { key: year, label: String(year) };
  }

  function quarterBucket(value) {
    var date = new Date(value);
    if (Number.isNaN(date.getTime())) return null;
    var quarter = Math.floor(date.getMonth() / 3) + 1;
    return {
      key: date.getFullYear() * 10 + quarter,
      label: "Q" + quarter + " " + date.getFullYear()
    };
  }

  function monthlyDistributionBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    if (!metadata || !rows.length || derivedMetricSpec(metadata, question) || !asksForDistribution(question) || !asksForTimeBreakdown(question)) return [];
    var groupColumn = resolveQuestionColumn(metadata, question, "category");
    if (!groupColumn || /date|month|orderid|^id$/i.test(columnKey(groupColumn))) return [];
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "month", "created_at", "updated_at"]);
    if (!dateColumn) return [];
    var mappings = parseValueMappings(question);
    var monthMap = {};
    var monthOrder = [];
    var groupTotals = {};
    var groupOrder = [];
    rows.forEach(function (row) {
      var bucket = monthBucket(rowValue(row, dateColumn));
      if (!bucket) return;
      var rawGroup = String(rowValue(row, groupColumn) || "").trim() || "Blank";
      var group = mappings[columnKey(rawGroup)] || rawGroup;
      if (!monthMap[bucket.label]) {
        monthMap[bucket.label] = { key: bucket.key, total: 0, groups: {} };
        monthOrder.push(bucket.label);
      }
      if (!groupTotals[group]) {
        groupTotals[group] = 0;
        groupOrder.push(group);
      }
      monthMap[bucket.label].groups[group] = (monthMap[bucket.label].groups[group] || 0) + 1;
      monthMap[bucket.label].total += 1;
      groupTotals[group] += 1;
    });
    monthOrder.sort(function (a, b) { return monthMap[a].key - monthMap[b].key; });
    var ascending = countSortAscending(question);
    groupOrder.sort(function (a, b) {
      return (ascending ? groupTotals[a] - groupTotals[b] : groupTotals[b] - groupTotals[a]) || a.localeCompare(b);
    });
    if (!monthOrder.length || !groupOrder.length) return [];
    var total = groupOrder.reduce(function (sum, group) { return sum + groupTotals[group]; }, 0);
    var most = groupOrder.slice().sort(function (a, b) { return groupTotals[b] - groupTotals[a] || a.localeCompare(b); })[0];
    var least = groupOrder.slice().sort(function (a, b) { return groupTotals[a] - groupTotals[b] || a.localeCompare(b); })[0];
    var tableRows = [["Month"].concat(groupOrder).concat(["Total"])];
    monthOrder.forEach(function (month) {
      tableRows.push([month].concat(groupOrder.map(function (group) {
        return String(monthMap[month].groups[group] || 0);
      })).concat([String(monthMap[month].total)]));
    });
    var values = groupOrder.map(function (group) {
      return monthOrder.map(function (month) { return monthMap[month].groups[group] || 0; }).join(",");
    }).join(";");
    var requested = requestedChartType(question);
    var chartType = requested === "stackedBar" || requested === "comparison" ? requested : "comparison";
    var palette = ["#2563eb", "#16a34a", "#f59e0b", "#dc2626", "#7c3aed", "#0891b2", "#db2777", "#65a30d"];
    var summary = "Exact monthly `" + groupColumn + "` order counts from " + total + " workbook rows grouped by `" + dateColumn + "` month and `" + groupColumn + "`. Overall most used: " + most + " (" + groupTotals[most] + "); least used: " + least + " (" + groupTotals[least] + ").";
    return [
      { type: "paragraph", data: { text: summary } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: chartType,
          title: groupColumn + " Count by Month",
          labels: monthOrder.join(","),
          values: values,
          seriesLabels: groupOrder.join(","),
          caption: "Generated from " + total + " parsed workbook rows. Formula: group rows by month from `" + dateColumn + "`, then count orders inside each `" + groupColumn + "` group. Series colors map to `" + groupColumn + "` values: " + groupOrder.join(", ") + ".",
          color: palette[0],
          colors: palette.slice(0, Math.max(1, groupOrder.length)).join(",")
        }
      }
    ];
  }

  function mentionedNumericColumns(metadata, question) {
    var numeric = Object.keys((metadata || {}).numericStats || {});
    var direct = numeric.filter(function (column) {
      return promptMentionsColumn(question, column);
    });
    var q = String(question || "");
    [
      ["wait time", ["wait time", "waittimeminutes", "waittime", "waiting time", "wait minutes"]],
      ["satisfaction score", ["satisfaction score", "satisfactionscore", "satisfaction", "patient satisfaction"]],
      ["length of stay", ["length of stay", "lengthofstaydays", "stay days", "los"]],
      ["total bill", ["total bill", "totalbill", "totalbill_inr", "billing", "bill amount"]],
      ["treatment cost", ["treatment cost", "treatmentcost", "treatmentcost_inr"]],
      ["medication cost", ["medication cost", "medicationcost", "medicationcost_inr"]],
      ["lab cost", ["lab cost", "labcost", "labcost_inr"]],
      ["revenue", ["revenue", "sales", "total_revenue"]],
      ["profit", ["profit", "net profit", "gross profit"]],
      ["units sold", ["units sold", "unitssold", "quantity", "qty"]]
    ].forEach(function (alias) {
      if (!new RegExp("\\b" + escapeRegExp(alias[0]).replace(/\\ /g, "\\s+") + "\\b", "i").test(q)) return;
      var column = findColumnByWords(metadata || {}, alias[1]);
      if (column && numeric.indexOf(column) >= 0 && direct.indexOf(column) < 0) direct.push(column);
    });
    return direct;
  }

  function isNumericAnalyticsColumn(metadata, column) {
    return !!(column && Object.prototype.hasOwnProperty.call((metadata || {}).numericStats || {}, column));
  }

  function addMeasure(measures, column, mode) {
    if (!column) return;
    mode = mode || "sum";
    if (measures.some(function (item) { return item.column === column && item.mode === mode; })) return;
    measures.push({ column: column, mode: mode });
  }

  function requestedNumericMeasures(metadata, question) {
    metadata = metadata || {};
    var q = String(question || "");
    var qKey = columnKey(q);
    var measures = [];
    function numericByWords(words) {
      var column = findColumnByWords(metadata, words || []);
      return isNumericAnalyticsColumn(metadata, column) ? column : "";
    }
    function addAlias(regex, words, mode) {
      if (!regex.test(q)) return;
      addMeasure(measures, numericByWords(words), mode);
    }
    addAlias(/\b(total\s+)?(total\s*bill|totalbill(?:_inr)?|bill\s+amount|billing|revenue|sales)\b/i, ["totalbill_inr", "total bill", "totalbill", "bill amount", "bill", "revenue", "sales", "amount", "total_revenue"], /\b(avg|average|mean)\b[^.]{0,50}\b(total\s*bill|totalbill|bill|revenue|sales)\b/i.test(q) ? "avg" : "sum");
    addAlias(/\b(avg|average|mean)\b[^.]{0,80}\btreatment\s+cost\b|\btreatment\s+cost\b[^.]{0,80}\b(avg|average|mean)\b/i, ["treatmentcost_inr", "treatment cost", "treatmentcost"], "avg");
    addAlias(/\b(avg|average|mean)\b[^.]{0,80}\bmedication\s+cost\b|\bmedication\s+cost\b[^.]{0,80}\b(avg|average|mean)\b/i, ["medicationcost_inr", "medication cost", "medicationcost", "medicine cost", "medicinecost"], "avg");
    addAlias(/\b(avg|average|mean)\b[^.]{0,80}\blab\s+cost\b|\blab\s+cost\b[^.]{0,80}\b(avg|average|mean)\b/i, ["labcost_inr", "lab cost", "labcost", "laboratory cost"], "avg");
    addAlias(/\b(avg|average|mean)\b[^.]{0,80}\blength\s+of\s+stay\b|\blength\s+of\s+stay\b[^.]{0,80}\b(avg|average|mean)\b|\baverage\s+stay\b|\bavg\s+stay\b/i, ["lengthofstaydays", "length of stay", "stay days", "los", "days stayed"], "avg");
    addAlias(/\b(avg|average|mean)\b[^.]{0,80}\bsatisfaction\s+score\b|\bsatisfaction\s+score\b[^.]{0,80}\b(avg|average|mean)\b|\baverage\s+satisfaction\b|\bavg\s+satisfaction\b/i, ["satisfactionscore", "satisfaction score", "satisfaction", "patient satisfaction"], "avg");
    addAlias(/\b(avg|average|mean)\b[^.]{0,80}\bwait\s+time\b|\bwait\s+time\b[^.]{0,80}\b(avg|average|mean)\b|\baverage\s+wait\b|\bavg\s+wait\b/i, ["waittimeminutes", "wait time", "waittime", "waiting time", "wait minutes"], "avg");
    mentionedNumericColumns(metadata, q).forEach(function (column) {
      if (!isNumericAnalyticsColumn(metadata, column)) return;
      var key = columnKey(column);
      var raw = escapeRegExp(column);
      var avgNear = new RegExp("\\b(avg|average|mean)\\b[^.;]{0,80}`?" + raw + "`?", "i").test(q) ||
        (qKey.indexOf("avg" + key) >= 0 || qKey.indexOf("average" + key) >= 0 || qKey.indexOf("mean" + key) >= 0);
      var sumNear = new RegExp("\\b(sum|total)\\b[^.;]{0,80}`?" + raw + "`?", "i").test(q) ||
        (qKey.indexOf("sum" + key) >= 0 || qKey.indexOf("total" + key) >= 0);
      var mode = avgNear && !sumNear ? "avg" : "sum";
      addMeasure(measures, column, mode);
    });
    return measures;
  }

  function measureFormula(measure) {
    if (!measure || !measure.column) return "";
    return (measure.mode === "avg" ? "AVG" : "SUM") + " `" + measure.column + "`";
  }

  function aggregateRows(rows, metric, mode) {
    if (mode === "count" || metric === "__row_count") return rows.length;
    var values = rows.map(function (row) { return numericValue(rowValue(row, metric)); }).filter(function (value) { return value !== null; });
    var sum = values.reduce(function (total, value) { return total + value; }, 0);
    if (mode === "average") return values.length ? sum / values.length : 0;
    if (mode === "median") return values.length ? medianNumber(values) : 0;
    if (mode === "min") return values.length ? Math.min.apply(Math, values) : 0;
    if (mode === "max") return values.length ? Math.max.apply(Math, values) : 0;
    return sum;
  }

  function resolveNamedColumn(metadata, words, fallbackKind) {
    return findColumnByWords(metadata, words) || resolveColumn(metadata, words.join(" "), fallbackKind || "category");
  }

  function categoryColumn(metadata) {
    return resolveNamedColumn(metadata, ["category", "department", "segment", "type", "class"], "category");
  }

  function paymentColumn(metadata) {
    return resolveNamedColumn(metadata, ["paymentmethod", "payment_method", "payment", "method"], "category");
  }

  function cityColumn(metadata) {
    return resolveNamedColumn(metadata, ["city", "town", "location"], "category");
  }

  function revenueColumn(metadata) {
    return resolveNamedColumn(metadata, ["revenue", "sales", "amount", "total_revenue", "total bill", "totalbill", "totalbill_inr", "bill amount"], "number");
  }

  function revenueEquivalentColumn(metadata) {
    return revenueColumn(metadata) || resolveNamedColumn(metadata, ["hospital billing", "billing", "total charge", "charges", "invoice amount", "total amount"], "number");
  }

  function strictRevenueColumn(metadata) {
    return findColumnByWords(metadata || {}, ["revenue", "total_revenue", "sales revenue", "net revenue"]);
  }

  function profitColumn(metadata) {
    return resolveNamedColumn(metadata, ["profit", "net_profit", "gross_profit", "marginamount"], "number");
  }

  function discountColumn(metadata) {
    return resolveNamedColumn(metadata, ["discount", "discount_amount", "discountvalue"], "number");
  }

  function quantityColumn(metadata) {
    return resolveNamedColumn(metadata, ["quantity", "qty", "units", "total_quantity", "totalquantity"], "number");
  }

  function drugColumn(metadata) {
    return resolveNamedColumn(metadata, ["drug name", "drug", "medicine", "product", "productname", "item", "sku"], "category");
  }

  function strictDrugColumn(metadata) {
    return findColumnByWords(metadata || {}, ["drug name", "drug", "medicine", "medicine name", "product", "productname"]);
  }

  function explicitManualColumnIssue(metadata, question) {
    metadata = metadata || {};
    var q = questionWithoutFilterValues(metadata, question);
    var missing = [];
    function requireColumn(regex, label, words, kind) {
      if (!regex.test(q)) return;
      var column = findColumnByWords(metadata, words || []);
      if (column && (!kind || kind !== "number" || Object.prototype.hasOwnProperty.call(metadata.numericStats || {}, column))) return;
      if (missing.indexOf(label) < 0) missing.push(label);
    }
    requireColumn(/\b(drug|drugs|medicine|medicines)\b/i, "Drug", ["drug name", "drug", "medicine", "medicine name", "product", "productname"]);
    if (/\brevenue\b/i.test(q) && !revenueEquivalentColumn(metadata)) {
      missing.push("Revenue");
    }
    requireColumn(/\bprofit\b/i, "Profit", ["profit", "net profit", "gross profit", "profit_inr"], "number");
    requireColumn(/\bcogs\b|\bcost\s+of\s+goods\b/i, "COGS", ["cogs", "cost of goods", "costofgoods"], "number");
    requireColumn(/\bunit\s+price\b|\bunitprice\b/i, "Unit Price", ["unit price", "unitprice", "price per unit"], "number");
    requireColumn(/\bunits?\s+sold\b|\bquantity\b|\bqty\b/i, "Units Sold", ["units sold", "unitssold", "quantity", "qty"], "number");
    requireColumn(/\breturn\s+quantity\b|\breturned\s+quantity\b|\breturns?\s+(?:qty|quantity|units?)\b/i, "Return Quantity", ["return quantity", "returnquantity", "returns", "returned quantity"], "number");
    requireColumn(/\bsupplier\s+lead\s*time\b|\blead\s*time\b/i, "Supplier Lead Time", ["supplier lead time", "supplierleadtime", "lead time", "leadtime"], "number");
    requireColumn(/\bcustomer\s+rating\b|\brating\b/i, "Customer Rating", ["customer rating", "customerrating", "rating"], "number");
    requireColumn(/\bqa\s+status\b/i, "QA Status", ["qa status", "qastatus", "quality status", "qualitystatus"]);
    requireColumn(/\brecall(?:ed)?\b|\brecall\s+flag\b/i, "Recall Flag", ["recall flag", "recallflag", "recalled", "recall"]);
    requireColumn(/\bstockout\b|\bstock\s*out\b/i, "Stockout", ["stockout", "stock out", "stockout flag"]);
    requireColumn(/\btemperature\s+deviation\b/i, "Temperature Deviation", ["temperature deviation", "temperaturedeviation", "temp deviation"], "number");
    requireColumn(/\bmanufacturer\b/i, "Manufacturer", ["manufacturer", "maker", "brand", "company"]);
    requireColumn(/\bsales\s+channel\b|\bchannel\b/i, "Sales Channel", ["sales channel", "saleschannel", "channel"]);
    requireColumn(/\bregion\b/i, "Region", ["region", "territory", "market"]);
    requireColumn(/\bcity\b/i, "City", ["city", "town", "location"]);
    requireColumn(/\bdepartment\b|\bdept\b/i, "Department", ["department", "dept", "service line"]);
    requireColumn(/\bpatient\s*id\b|\bpatient ids\b/i, "PatientID", ["patient id", "patientid"]);
    requireColumn(/\bencounter\s*id\b/i, "EncounterID", ["encounter id", "encounterid"]);
    (q.match(/\b[A-Za-z][A-Za-z0-9_]*(?:ID|Id|_id|_ID)\b/g) || []).forEach(function (token) {
      var key = columnKey(token);
      var exists = (metadata.headings || []).some(function (heading) { return columnKey(heading) === key; });
      if (!exists && missing.indexOf(token) < 0) missing.push(token);
    });
    requireColumn(/\btotal\s*bill\b|\bhospital\s+billing\b|\bbilling\b/i, "Total Bill", ["totalbill_inr", "total bill", "totalbill", "hospital billing", "bill amount"], "number");
    requireColumn(/\btreatment\s+cost\b/i, "Treatment Cost", ["treatmentcost_inr", "treatment cost", "treatmentcost"], "number");
    requireColumn(/\bmedication\s+cost\b/i, "Medication Cost", ["medicationcost_inr", "medication cost", "medicationcost"], "number");
    requireColumn(/\blab\s+cost\b/i, "Lab Cost", ["labcost_inr", "lab cost", "labcost"], "number");
    requireColumn(/\binsurance\s+claim\b/i, "Insurance Claim", ["insuranceclaim_inr", "insurance claim", "insuranceclaim"], "number");
    requireColumn(/\bclaim\s+status\b/i, "Claim Status", ["claim status", "claimstatus"]);
    requireColumn(/\breadmission\b/i, "Readmission", ["readmission30days", "readmission", "readmitted"]);
    requireColumn(/\bicu\b/i, "ICU", ["icu", "icu flag", "icuflag"]);
    requireColumn(/\bmortality\b|\bdeath\b/i, "Outcome", ["outcome", "mortality", "death", "status"]);
    requireColumn(/\blength\s+of\s+stay\b|\blos\b/i, "Length Of Stay", ["lengthofstaydays", "length of stay", "los"], "number");
    requireColumn(/\bwait\s+time\b/i, "Wait Time", ["waittimeminutes", "wait time", "waittime"], "number");
    requireColumn(/\bsatisfaction\s+score\b|\bsatisfaction\b/i, "Satisfaction Score", ["satisfactionscore", "satisfaction score", "satisfaction"], "number");
    requireColumn(/\badmission\s+date\b/i, "AdmissionDate", ["admission date", "admissiondate"]);
    requireColumn(/\bdischarge\s+date\b/i, "DischargeDate", ["discharge date", "dischargedate"]);
    requireColumn(/\bsale\s+date\b/i, "SaleDate", ["sale date", "saledate"]);
    requireColumn(/\bexpiry\s+date\b/i, "ExpiryDate", ["expiry date", "expirydate", "expiration date"]);
    if (!missing.length) return "";
    return missing.join(" or ") + " column was not found in this sheet.";
  }

  function manufacturerColumn(metadata) {
    return resolveNamedColumn(metadata, ["manufacturer", "maker", "brand", "company", "supplier", "vendor"], "category");
  }

  function regionColumn(metadata) {
    return resolveNamedColumn(metadata, ["region", "territory", "market", "country", "state", "city", "location"], "category");
  }

  function customerTypeColumn(metadata) {
    return resolveNamedColumn(metadata, ["customer type", "customer_type", "client type", "buyer type", "customer segment", "segment", "channel"], "category");
  }

  function prettyMetricName(column) {
    return String(column || "").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
  }

  function uniqueColumnValues(metadata, column, limit) {
    var rows = rowsForAnalytics(metadata);
    var values = {};
    var order = [];
    rows.forEach(function (row) {
      var value = String(rowValue(row, column) || "").trim();
      if (!value || values[value]) return;
      values[value] = true;
      order.push(value);
    });
    return order.slice(0, limit || 100);
  }

  function promptMentionsColumn(question, column) {
    var rawQuestion = String(question || "");
    var q = columnKey(rawQuestion);
    var key = columnKey(column);
    if (key && key.length <= 3) {
      var tokens = rawQuestion.toLowerCase().match(/[a-z0-9_]+/g) || [];
      return tokens.indexOf(key) >= 0;
    }
    if (!key) return false;
    var rawColumn = String(column || "").replace(/[_-]+/g, " ").trim();
    if (rawColumn) {
      var phrase = rawColumn.split(/\s+/).map(escapeRegExp).join("[\\s_-]*");
      if (new RegExp("(^|[^A-Za-z0-9])" + phrase + "([^A-Za-z0-9]|$)", "i").test(rawQuestion)) return true;
    }
    if (q.indexOf(key) >= 0) {
      var before = q.charAt(q.indexOf(key) - 1);
      var after = q.charAt(q.indexOf(key) + key.length);
      return !before && !after;
    }
    return key.indexOf(q) >= 0 && q.length >= 5;
  }

  function promptMentionsIdentifierColumn(question, column) {
    var q = columnKey(question);
    var key = columnKey(column);
    if (!key) return false;
    if (!/(^|_)?id$/.test(key) && !/(uuid|guid|code|number|no)$/.test(key)) return false;
    return q.indexOf(key) >= 0 || promptMentionsColumn(question, column);
  }

  function columnsMentionedInPhrase(metadata, phrase) {
    var headings = (metadata || {}).headings || [];
    var keyPhrase = columnKey(phrase);
    var matches = [];
    headings.forEach(function (heading) {
      var key = columnKey(heading);
      if (key && keyPhrase.indexOf(key) >= 0 && matches.indexOf(heading) === -1) matches.push(heading);
    });
    return matches;
  }

  function splitPromptDimensionColumns(metadata, phrase) {
    var cleaned = String(phrase || "")
      .replace(/[`'"]/g, "")
      .replace(/\btogether\b/ig, " ")
      .replace(/\s+/g, " ")
      .trim();
    if (!cleaned) return [];
    var parts = cleaned.split(/\s*(?:\+|&|\/|\band\b|\bplus\b)\s*/i).filter(Boolean);
    var columns = [];
    parts.forEach(function (part) {
      var column = resolvePromptColumnPhrase(metadata, part, "category");
      if (column && columns.indexOf(column) === -1) columns.push(column);
    });
    columnsMentionedInPhrase(metadata, cleaned).forEach(function (column) {
      if (columns.indexOf(column) === -1) columns.push(column);
    });
    return columns;
  }

  function promptGroupColumns(metadata, question, fallbackColumn) {
    var q = String(question || "");
    var groupingQuestion = q
      .replace(/\bdo\s+not\s+group\s+by\s+[^,.;!?]+/ig, " ")
      .replace(/\b(?:not|without|exclude|excluding|avoid|ignore)\s+(?:group(?:ing)?\s+)?by\s+[^,.;!?]+/ig, " ");
    var explicitRows = extractPromptDimension(q, "rows?");
    var byMatch = groupingQuestion.match(/\b(?:by|group\s+by|grouped\s+by|across)\s+([^,.;]+?)(?:\s+(?:using|with|where|including|include|and\s+show|sort|from|over|per|only|not|exclude|ignore|validate|state)\b|[,.;]|$)/i);
    var wiseMatch = groupingQuestion.match(/\b([A-Za-z][A-Za-z0-9_ ]{1,70}?)\s+wise\b/i);
    var eachMatch = groupingQuestion.match(/\beach\s+([^,.;]+?)(?:\s+(?:using|with|where|including|include|and\s+show|sort|from|over|per|only|not|exclude|ignore|validate|state|valid|rows?)\b|[,.;]|$)/i);
    var phrase = explicitRows || (byMatch ? byMatch[1] : (wiseMatch ? wiseMatch[1] : (eachMatch ? eachMatch[1] : "")));
    var columns = splitPromptDimensionColumns(metadata, phrase).filter(function (column) {
      return !isForbiddenColumn(metadata, question, column);
    });
    if (wiseMatch) {
      var categorical = (metadata || {}).categoricalColumns || [];
      var wiseColumns = columnsMentionedInPhrase(metadata, wiseMatch[1]).filter(function (column) {
        return categorical.indexOf(column) !== -1 && !isForbiddenColumn(metadata, question, column);
      });
      columns = wiseColumns;
    }
    var headings = (metadata || {}).headings || [];
    headings.forEach(function (heading) {
      if (columns.indexOf(heading) === -1 && promptMentionsColumn(q, heading) && (metadata.categoricalColumns || []).indexOf(heading) !== -1 && !isForbiddenColumn(metadata, question, heading)) columns.push(heading);
    });
    if (!columns.length && fallbackColumn && !isForbiddenColumn(metadata, question, fallbackColumn)) columns.push(fallbackColumn);
    return columns.filter(Boolean);
  }

  function questionWithoutFilterValues(metadata, question) {
    var q = String(question || "");
    try {
      manualFilters(metadata || {}, q).forEach(function (filter) {
        var value = String(filter && filter.value || "").trim();
        if (!value) return;
        q = q.replace(new RegExp("\\b" + escapeRegExp(value).replace(/\\ /g, "\\s+") + "\\b", "ig"), " ");
      });
    } catch (error) {
      return q;
    }
    return q.replace(/\s+/g, " ").trim();
  }

  function resolveMetricByPrompt(metadata, question, aliases, fallback) {
    var q = String(question || "");
    var words = aliases || [];
    var exact = resolvePromptColumnPhrase(metadata, q, "number");
    if (exact && Object.prototype.hasOwnProperty.call((metadata || {}).numericStats || {}, exact) && promptMentionsColumn(q, exact)) return exact;
    for (var i = 0; i < words.length; i += 1) {
      var column = resolveNamedColumn(metadata, words[i], "number");
      if (column && promptMentionsColumn(q + " " + words[i].join(" "), column)) return column;
    }
    return fallback || "";
  }

  function formatValue(value, suffix) {
    return String(chartNumber(value)) + (suffix || "");
  }

  function groupedByColumns(rows, columns) {
    var grouped = {};
    var order = [];
    rows.forEach(function (row) {
      var parts = groupKeyParts(row, columns);
      var key = parts.join("||");
      if (!grouped[key]) {
        grouped[key] = { parts: parts, rows: [], count: 0 };
        order.push(key);
      }
      grouped[key].rows.push(row);
      grouped[key].count += 1;
    });
    return { grouped: grouped, order: order };
  }

  function requestedLimit(question, fallback) {
    var match = String(question || "").match(/\b(?:top|bottom|first|last)\s+(\d+)\b/i);
    return match ? Math.max(1, Math.min(100, Number(match[1]) || fallback || 10)) : (fallback || 10);
  }

  function groupKeyParts(row, columns) {
    return columns.map(function (column) { return String(rowValue(row, column) || "Blank").trim() || "Blank"; });
  }

  function splitGroupByColumns(groupBy) {
    return String(groupBy || "").split(",").map(function (item) { return item.trim(); }).filter(Boolean);
  }

  function normalizePlanDimensions(dimensions) {
    var out = [];
    (dimensions || []).forEach(function (dimension) {
      String(dimension || "").split(",").forEach(function (part) {
        part = part.trim();
        if (part && out.indexOf(part) === -1) out.push(part);
      });
    });
    return out;
  }

  function pivotMatrixIntent(metadata, question) {
    var q = String(question || "");
    if (!/\b(matrix|heatmap|pivot|cross[-\s]*tab|cross\s*tab|rows?\s+(?:should\s+be|are)|columns?\s+(?:should\s+be|are)|series\s+should\s+be|stacked\s+bar)\b/i.test(q)) return null;
    var rowColumn = "";
    var rowColumns = null;
    var columnColumn = "";
    var valueColumn = "";
    var valueMode = "sum";
    var explicitRows = extractPromptDimension(q, "rows?");
    var explicitColumns = extractPromptDimension(q, "columns?");
    var explicitSeries = extractPromptDimension(q, "series");
    var explicitValues = extractPromptDimension(q, "values?");
    if (explicitRows) {
      if (/\bmonth|date|time|quarter\b/i.test(explicitRows)) rowColumn = "__month__";
      else {
        rowColumns = splitPromptDimensionColumns(metadata, explicitRows);
        rowColumn = rowColumns.length > 1 ? rowColumns.join(" + ") : (rowColumns[0] || resolvePromptColumnPhrase(metadata, explicitRows, "category"));
      }
    }
    if (explicitColumns) {
      columnColumn = resolvePromptColumnPhrase(metadata, explicitColumns, "category");
      if (!columnColumn) {
        var explicitColumnHits = columnsMentionedInPhrase(metadata, explicitColumns);
        columnColumn = explicitColumnHits[0] || "";
      }
    }
    if (!columnColumn && explicitSeries) columnColumn = resolvePromptColumnPhrase(metadata, explicitSeries, "category");
    if (explicitValues) {
      if (/\bcount|counts|orders?|rows?|encounters?\b/i.test(explicitValues)) valueMode = "count";
      else valueColumn = resolvePromptColumnPhrase(metadata, explicitValues, "number");
    }
    if ((!rowColumn || !columnColumn) && /\b(matrix|pivot|cross[-\s]*tab|cross\s*tab)\b/i.test(q)) {
      var pivotGroups = promptGroupColumns(metadata, q, "");
      if (pivotGroups.length >= 2) {
        rowColumns = [pivotGroups[0]];
        rowColumn = pivotGroups[0];
        columnColumn = columnColumn || pivotGroups[1];
      }
    }
    if ((!rowColumn || !columnColumn) && /\b(?:pivot|cross[-\s]*tab|cross\s*tab)\b/i.test(q)) {
      var naturalBy = q.match(/\bby\s+(.+?)(?:[.;]|$)/i);
      var naturalColumns = naturalBy ? splitPromptDimensionColumns(metadata, naturalBy[1]) : [];
      if (naturalColumns.length < 2 && naturalBy) {
        naturalColumns = columnsMentionedInPhrase(metadata, naturalBy[1]).filter(function (column, index, list) {
          return column && list.indexOf(column) === index;
        });
      }
      if (naturalColumns.length >= 2) {
        rowColumns = [naturalColumns[0]];
        rowColumn = naturalColumns[0];
        columnColumn = naturalColumns[1];
      }
    }
    if (/\brows?\s+should\s+be\s+(?:city|cities)\s+and\s+categor(?:y|ies)\s+together\b|\bcity\s+and\s+category\s+together\b/i.test(q)) {
      rowColumns = [cityColumn(metadata), categoryColumn(metadata)].filter(Boolean);
      rowColumn = rowColumns.length ? rowColumns.join(" + ") : "";
    }
    if (!explicitRows && /\b(month|monthly|month-level|x-axis\s+should\s+be\s+month)\b/i.test(q)) rowColumn = "__month__";
    if (!rowColumns && /\brows?\s+should\s+be\s+cities?\b|\bcity\s+by\b/i.test(q)) rowColumn = cityColumn(metadata);
    if (!rowColumns && /\brows?\s+should\s+be\s+categor(?:y|ies)\b|\bby\s+category\s+count\s+matrix\b/i.test(q)) rowColumn = categoryColumn(metadata);
    if (!columnColumn && (/\bcolumns?\s+(?:should\s+be|are)\s+payment\s*methods?\b|\bpayment\s*method\b/i.test(q) && /\bmatrix|pivot|columns?\s+(?:should\s+be|are)\b/i.test(q))) columnColumn = paymentColumn(metadata);
    if (!columnColumn && /\bseries\s+should\s+be\s+categor(?:y|ies)\b|\bcolumns?\s+(?:should\s+be|are)\s+categor(?:y|ies)\b|\bmonthly\s+category\b|\bby\s+category\b/i.test(q)) columnColumn = categoryColumn(metadata);
    if (/\bcustomer\s+segments?\b|\bsegment\b/i.test(q)) {
      var segment = findColumnByWords(metadata, ["customer segment", "segment"]);
      if (/\bseries\s+should\s+be|\bcolumns?\s+should\s+be|\bby\s+customer\s+segment|\bby\s+segment\b/i.test(q)) columnColumn = columnColumn || segment;
      else rowColumn = segment || rowColumn;
    }
    if (/\brows?\s+should\s+be\s+months?\b/i.test(q)) rowColumn = "__month__";
    if (/\b(payment\s*method\s+by\s+category|paymentmethod\s+by\s+category)\b/i.test(q)) {
      rowColumn = categoryColumn(metadata);
      columnColumn = paymentColumn(metadata);
    }
    if (/\border\s+count|order\s+counts|count\s+orders|encounter\s+count|encounter\s+counts|count\s+encounters|record\s+count|row\s+count|values?\s+should\s+be\s+(?:order|encounter|record|row)?\s*counts?\b/i.test(q)) valueMode = "count";
    else if (!valueColumn && /\bprofit\b/i.test(q)) valueColumn = profitColumn(metadata);
    else if (!valueColumn && /\bdiscount\b/i.test(q)) valueColumn = discountColumn(metadata);
    else if (!valueColumn && /\brevenue|sales|amount\b/i.test(q)) valueColumn = revenueColumn(metadata);
    if (!rowColumn || !columnColumn || (!valueColumn && valueMode !== "count")) return null;
    var pivotColumns = (rowColumns && rowColumns.length ? rowColumns : [rowColumn]).concat([columnColumn, valueColumn]).filter(function (column) { return column && column !== "__month__"; });
    if (pivotColumns.some(function (column) { return isForbiddenColumn(metadata, question, column); })) return null;
    if (rowColumns && rowColumns.indexOf(columnColumn) !== -1) return null;
    if (!rowColumns && rowColumn !== "__month__" && rowColumn === columnColumn) return null;
    return { rowColumn: rowColumn, rowColumns: rowColumns, columnColumn: columnColumn, valueColumn: valueColumn, valueMode: valueMode };
  }

  function combinationRankingIntent(metadata, question) {
    var q = String(question || "");
    var wantsCombination = /\b(combinations?|combo|together|\+)\b/i.test(q);
    if (!wantsCombination) return null;
    var groupCols = promptGroupColumns(metadata, q, "").filter(function (column) {
      return (metadata.categoricalColumns || []).indexOf(column) !== -1;
    });
    if (groupCols.length < 2 && /\b(city-category|city\s+category|city\s+and\s+category)\b/i.test(q)) {
      groupCols = [cityColumn(metadata), categoryColumn(metadata)].filter(Boolean);
    }
    if (groupCols.length < 2) return null;
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    var quantity = quantityColumn(metadata);
    var sortMetric = /\bprofit\s+margin|margin\b/i.test(q) && revenue && profit ? "Profit Margin %" :
      (/\bprofit\b/i.test(q) && profit ? "Profit" :
        (/\bunits?\s+sold|quantity\b/i.test(q) && quantity ? "Units Sold" :
          (revenue ? "Revenue" : "Order Count")));
    return {
      columns: groupCols,
      revenue: revenue,
      profit: profit,
      quantity: quantity,
      sortMetric: sortMetric,
      direction: /\bbottom|lowest|least\b/i.test(q) ? "asc" : "desc",
      limit: requestedLimit(q, 10)
    };
  }

  function formulaRatioIntent(metadata, question) {
    var q = String(question || "");
    if (!/\b(profit\s+margin|discount\s*%|discount\s+percentage|profit\s+share|share\s+by\s+total\s+profit|high\s+discount|low\s+margin)\b/i.test(q)) return null;
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    var discount = discountColumn(metadata);
    if (!revenue) return null;
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "month"]);
    var monthWise = asksForTimeBreakdown(q) && dateColumn;
    var groupColumn = monthWise ? "__month__" : (categoryColumn(metadata) || resolveGroupColumn(metadata, q));
    if (!groupColumn) return null;
    if (/\bprofit\s+margin\b/i.test(q) && !profit) return null;
    if (/\bdiscount\s*%|discount\s+percentage|high\s+discount\b/i.test(q) && !discount) return null;
    var chartMetric = profit && /\bprofit\s+margin\b/i.test(q) ? "Profit Margin %" : (profit && /\bprofit\s+share|share\s+by\s+total\s+profit|total\s+profit\b/i.test(q) ? "Profit Share %" : "Discount %");
    return {
      revenue: revenue,
      profit: profit,
      discount: discount,
      dateColumn: dateColumn,
      groupColumn: groupColumn,
      groupLabel: groupColumn === "__month__" ? "Month" : groupColumn,
      chartMetric: chartMetric
    };
  }

  function pivotMatrixBlocks(metadata, question) {
    var intent = pivotMatrixIntent(metadata, question);
    var rows = rowsForAnalytics(metadata);
    if (!intent || !rows.length) return [];
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "month"]);
    if (intent.rowColumn === "__month__" && !dateColumn) return [];
    var rowColumns = intent.rowColumns && intent.rowColumns.length ? intent.rowColumns : [intent.rowColumn];
    var rowMap = {};
    var rowOrder = [];
    var columnTotals = {};
    var columnOrder = [];
    rows.forEach(function (row) {
      var rowLabel = "";
      var rowSort = 0;
      if (intent.rowColumn === "__month__") {
        var bucket = monthBucket(rowValue(row, dateColumn));
        if (!bucket) return;
        rowLabel = bucket.label;
        rowSort = bucket.key;
      } else {
        rowLabel = groupKeyParts(row, rowColumns).join(" / ");
      }
      var colLabel = String(rowValue(row, intent.columnColumn) || "Blank").trim() || "Blank";
      var value = intent.valueMode === "count" ? 1 : rowNumber(row, intent.valueColumn);
      if (!rowMap[rowLabel]) {
        rowMap[rowLabel] = { key: rowSort || rowOrder.length, rowValues: intent.rowColumn === "__month__" ? [rowLabel] : rowLabel.split(" / "), total: 0, columns: {} };
        rowOrder.push(rowLabel);
      }
      if (!columnTotals[colLabel]) {
        columnTotals[colLabel] = 0;
        columnOrder.push(colLabel);
      }
      rowMap[rowLabel].columns[colLabel] = (rowMap[rowLabel].columns[colLabel] || 0) + value;
      rowMap[rowLabel].total += value;
      columnTotals[colLabel] += value;
    });
    rowOrder.sort(function (a, b) { return rowMap[a].key - rowMap[b].key || a.localeCompare(b); });
    columnOrder.sort(function (a, b) { return columnTotals[b] - columnTotals[a] || a.localeCompare(b); });
    var countLabel = /\bencounter|visit|admission|patient\b/i.test(String(question || "")) ? "Encounter Count" : "Order Count";
    var countNoun = /\bencounter|visit|admission|patient\b/i.test(String(question || "")) ? "encounter counts" : "order counts";
    var countCaption = /\bencounter|visit|admission|patient\b/i.test(String(question || "")) ? "count rows/encounters" : "count rows/orders";
    var valueLabel = intent.valueMode === "count" ? countLabel : intent.valueColumn;
    var rowHeaders = intent.rowColumn === "__month__" ? ["Month"] : rowColumns;
    var rowHeader = rowHeaders.join(" + ");
    var tableRows = [rowHeaders.concat(columnOrder).concat(["Total"])];
    var pairRows = [[rowHeader + " | " + intent.columnColumn, valueLabel]];
    rowOrder.forEach(function (label) {
      tableRows.push(rowMap[label].rowValues.concat(columnOrder.map(function (column) {
        var cellValue = chartNumber(rowMap[label].columns[column] || 0);
        pairRows.push([label + " | " + column + " (" + label + "|" + column + ")", String(cellValue)]);
        return String(cellValue);
      })).concat([String(chartNumber(rowMap[label].total))]));
    });
    var values = columnOrder.map(function (column) {
      return rowOrder.map(function (label) { return chartNumber(rowMap[label].columns[column] || 0); }).join(",");
    }).join(";");
    var requested = requestedChartType(question);
    var includeChart = /\b(stacked|comparison|chart|graph|plot|visual|visualize)\b/i.test(analyticsChartIntentText(question));
    var chartType = requested === "stackedBar" || requested === "comparison" ? requested : (/\bstacked\s+bar\b/i.test(String(question || "")) ? "stackedBar" : "comparison");
    var palette = ["#2563eb", "#16a34a", "#f59e0b", "#dc2626", "#7c3aed", "#0891b2", "#db2777", "#65a30d"];
    var blocks = [
      { type: "paragraph", data: { text: "Computed " + valueLabel + " matrix from " + rows.length + " parsed workbook rows. Rows = `" + rowHeader + "`, columns = `" + intent.columnColumn + "`, values = " + (intent.valueMode === "count" ? countNoun : "sum `" + intent.valueColumn + "`") + "." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      { type: "table", data: { withHeadings: true, rows: pairRows } }
    ];
    if (includeChart) {
      blocks.push({
        type: "chart",
        data: {
          type: chartType,
          title: valueLabel + " by " + rowHeader + " and " + intent.columnColumn,
          labels: rowOrder.join(","),
          values: values,
          seriesLabels: columnOrder.join(","),
          caption: "Generated from " + rows.length + " parsed workbook rows. Formula: group by `" + rowHeader + "` and `" + intent.columnColumn + "`, then " + (intent.valueMode === "count" ? countCaption : "sum `" + intent.valueColumn + "`") + ". Series colors map to `" + intent.columnColumn + "` values.",
          color: palette[0],
          colors: palette.slice(0, Math.max(1, columnOrder.length)).join(",")
        }
      });
    }
    return blocks;
  }

  function aggregationMode(question) {
    var q = String(question || "");
    if (/\b(avg|average|avrage|averge|mean|per\s+order|per\s+row)\b/i.test(q)) return "average";
    if (/\bmedian\b/i.test(q)) return "median";
    if (/\b(total|totl|tatal|sum)\b/i.test(q)) return "sum";
    if (/\b(min|minimum|lowest|least)\b/i.test(q) && !/\bcount|share|distribution|mix\b/i.test(q)) return "min";
    if (/\b(max|maximum|highest|most)\b/i.test(q) && !/\bcount|share|distribution|mix\b/i.test(q)) return "max";
    if (/\b(count|counts|number\s+of|how\s+many)\b/i.test(q)) return "count";
    return "sum";
  }

  function formulaRatioBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    var intent = formulaRatioIntent(metadata, q);
    if (!metadata || !rows.length || !intent) return [];
    var revenue = intent.revenue;
    var profit = intent.profit;
    var discount = intent.discount;
    var dateColumn = intent.dateColumn;
    var groupColumn = intent.groupColumn;
    var monthWise = groupColumn === "__month__";
    var grouped = {};
    var order = [];
    rows.forEach(function (row) {
      var label = "";
      var key = order.length;
      if (groupColumn === "__month__") {
        var bucket = monthBucket(rowValue(row, dateColumn));
        if (!bucket) return;
        label = bucket.label;
        key = bucket.key;
      } else {
        label = String(rowValue(row, groupColumn) || "Blank").trim() || "Blank";
      }
      if (!grouped[label]) {
        grouped[label] = { key: key, revenue: 0, profit: 0, discount: 0, rows: 0 };
        order.push(label);
      }
      grouped[label].revenue += rowNumber(row, revenue);
      grouped[label].profit += profit ? rowNumber(row, profit) : 0;
      grouped[label].discount += discount ? rowNumber(row, discount) : 0;
      grouped[label].rows += 1;
    });
    order.sort(function (a, b) { return grouped[a].key - grouped[b].key || a.localeCompare(b); });
    var totalProfit = order.reduce(function (sum, label) { return sum + grouped[label].profit; }, 0);
    var wantsCondition = /\b(high\s+discount|low\s+margin|where|find)\b/i.test(q) && discount && profit;
    var avgDiscountPct = order.reduce(function (sum, label) { return sum + (grouped[label].revenue ? grouped[label].discount / grouped[label].revenue * 100 : 0); }, 0) / Math.max(1, order.length);
    var avgMarginPct = order.reduce(function (sum, label) { return sum + (grouped[label].revenue ? grouped[label].profit / grouped[label].revenue * 100 : 0); }, 0) / Math.max(1, order.length);
    var wantsProfitShare = /\bprofit\s+share|share\s+by\s+total\s+profit|total\s+profit\b/i.test(q) && profit;
    var tableRows = wantsProfitShare ? [["Group", "Profit", "Profit Share %"]] : [["Group", "Revenue"]];
    if (!wantsProfitShare && discount) tableRows[0].push("Discount", "Discount %");
    if (!wantsProfitShare && profit) tableRows[0].push("Profit", "Profit Margin %");
    if (!wantsProfitShare) tableRows[0].push("Rows");
    if (wantsCondition) tableRows[0].push("Finding");
    var filteredOrder = order.filter(function (label) {
      if (!wantsCondition) return true;
      var item = grouped[label];
      var dp = item.revenue ? item.discount / item.revenue * 100 : 0;
      var mp = item.revenue ? item.profit / item.revenue * 100 : 0;
      return dp >= avgDiscountPct && mp <= avgMarginPct;
    });
    if (!filteredOrder.length) filteredOrder = order.slice();
    filteredOrder.forEach(function (label) {
      var item = grouped[label];
      var discountPct = item.revenue ? item.discount / item.revenue * 100 : 0;
      var marginPct = item.revenue ? item.profit / item.revenue * 100 : 0;
      var row = wantsProfitShare ? [label, String(chartNumber(item.profit)), totalProfit ? chartNumber(item.profit * 100 / totalProfit) + "%" : "0%"] : [label, String(chartNumber(item.revenue))];
      if (!wantsProfitShare && discount) row.push(String(chartNumber(item.discount)), chartNumber(discountPct) + "%");
      if (!wantsProfitShare && profit) row.push(String(chartNumber(item.profit)), chartNumber(marginPct) + "%");
      if (!wantsProfitShare) row.push(String(item.rows));
      if (wantsCondition) row.push((discountPct >= avgDiscountPct ? "High discount" : "Lower discount") + "; " + (marginPct <= avgMarginPct ? "low margin" : "higher margin"));
      tableRows.push(row);
    });
    var chartMetric = intent.chartMetric;
    var chartValues = filteredOrder.map(function (label) {
      var item = grouped[label];
      if (chartMetric === "Profit Margin %") return chartNumber(item.revenue ? item.profit / item.revenue * 100 : 0);
      if (chartMetric === "Profit Share %") return chartNumber(totalProfit ? item.profit * 100 / totalProfit : 0);
      return chartNumber(item.revenue ? item.discount / item.revenue * 100 : 0);
    });
    var chartType = manualChartType(q, asksForTrend(q) || monthWise ? "line" : "bar");
    var groupLabel = groupColumn === "__month__" ? "Month" : groupColumn;
    return [
      { type: "paragraph", data: { text: "Computed formula metrics from " + rows.length + " parsed workbook rows grouped by `" + groupLabel + "`. Formula: Discount % = Discount / Revenue x 100; Profit Margin % = Profit / Revenue x 100; Profit Share % = group Profit / total Profit x 100." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: chartType,
          title: chartMetric + " by " + groupLabel,
          labels: filteredOrder.join(","),
          values: chartValues.join(","),
          caption: "Generated from " + rows.length + " parsed workbook rows using explicit formula intent from the prompt.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      }
    ];
  }

  function genericPerUnitBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(per\s+unit|revenue\s*\/\s*units?|revenue\s+per\s+unit|average\s+revenue\s+per\s+unit|aov|average\s+order\s+value)\b/i.test(q)) return [];
    var revenue = revenueColumn(metadata);
    var denominator = /\baov|average\s+order\s+value|order\s+count\b/i.test(q) && !/\bunit|units\s+sold\b/i.test(q) ? "" : quantityColumn(metadata);
    var orderId = orderIdentifierColumn(metadata);
    if (!denominator && orderId) denominator = "__orders__";
    if (!revenue || !denominator) return [];
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "sale date", "month"]);
    var monthWise = asksForTimeBreakdown(q) && dateColumn;
    var groupCols = promptGroupColumns(metadata, q, /\bdrug|product|item|sku\b/i.test(q) ? drugColumn(metadata) : resolveGroupColumn(metadata, q));
    if (monthWise) groupCols = ["__month__"].concat(groupCols.filter(function (column) { return column !== "__month__"; }));
    if (!groupCols.length) groupCols = [drugColumn(metadata) || categoryColumn(metadata) || resolveGroupColumn(metadata, q)].filter(Boolean);
    if (!groupCols.length) return [];
    var by = {};
    var order = [];
    rows.forEach(function (row) {
      var parts = groupCols.map(function (column) {
        if (column === "__month__") {
          var bucket = monthBucket(rowValue(row, dateColumn));
          return bucket ? bucket.label : "";
        }
        return String(rowValue(row, column) || "Blank").trim() || "Blank";
      });
      if (parts.some(function (part) { return !part; })) return;
      var key = parts.join("||");
      if (!by[key]) {
        by[key] = { parts: parts, revenue: 0, denominator: 0, rows: 0, sortKey: order.length };
        if (groupCols[0] === "__month__") {
          var firstBucket = monthBucket(rowValue(row, dateColumn));
          by[key].sortKey = firstBucket ? firstBucket.key * 1000 + order.length : order.length;
        }
        order.push(key);
      }
      by[key].revenue += rowNumber(row, revenue);
      by[key].denominator += denominator === "__orders__" ? 1 : rowNumber(row, denominator);
      by[key].rows += 1;
    });
    order.sort(function (a, b) { return by[a].sortKey - by[b].sortKey || a.localeCompare(b); });
    var metricLabel = denominator === "__orders__" ? "AOV" : "Average Revenue per Unit";
    var header = groupCols.map(function (column) { return column === "__month__" ? "Month" : column; }).concat([revenue, denominator === "__orders__" ? "Order Count" : denominator, metricLabel]);
    var tableRows = [header].concat(order.map(function (key) {
      var item = by[key];
      return item.parts.concat([String(chartNumber(item.revenue)), String(chartNumber(item.denominator)), String(chartNumber(item.denominator ? item.revenue / item.denominator : 0))]);
    }));
    var chartType = manualChartType(q, asksForTrend(q) || monthWise ? "line" : "bar");
    var labels = [];
    var values = "";
    var seriesLabels = "";
    if (monthWise && groupCols.length > 1) {
      var monthOrder = [];
      var monthSeen = {};
      var seriesOrder = [];
      var seriesSeen = {};
      order.forEach(function (key) {
        var month = by[key].parts[0];
        var series = by[key].parts.slice(1).join(" / ");
        if (!monthSeen[month]) { monthSeen[month] = true; monthOrder.push(month); }
        if (!seriesSeen[series]) { seriesSeen[series] = true; seriesOrder.push(series); }
      });
      labels = monthOrder;
      values = seriesOrder.map(function (series) {
        return monthOrder.map(function (month) {
          var found = order.filter(function (key) { return by[key].parts[0] === month && by[key].parts.slice(1).join(" / ") === series; })[0];
          return found ? chartNumber(by[found].denominator ? by[found].revenue / by[found].denominator : 0) : 0;
        }).join(",");
      }).join(";");
      seriesLabels = seriesOrder.join(",");
      chartType = chartType === "bar" ? "line" : chartType;
    } else {
      var chartOrder = order.slice().sort(function (a, b) {
        var av = by[a].denominator ? by[a].revenue / by[a].denominator : 0;
        var bv = by[b].denominator ? by[b].revenue / by[b].denominator : 0;
        return bv - av || a.localeCompare(b);
      }).slice(0, requestedLimit(q, 12));
      labels = chartOrder.map(function (key) { return by[key].parts.join(" / "); });
      values = chartOrder.map(function (key) { return chartNumber(by[key].denominator ? by[key].revenue / by[key].denominator : 0); }).join(",");
    }
    return [
      { type: "paragraph", data: { text: "Computed " + metricLabel + " from " + rows.length + " parsed workbook rows. Formula: SUM `" + revenue + "` / " + (denominator === "__orders__" ? "order count" : "SUM `" + denominator + "`") + " for each " + header.slice(0, -3).join(" + ") + " group." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: chartType,
          title: metricLabel + " by " + header.slice(0, -3).join(" and "),
          labels: labels.join(","),
          values: values,
          seriesLabels: seriesLabels,
          caption: "Exact grouped ratio. Numerator = SUM `" + revenue + "`; denominator = " + (denominator === "__orders__" ? "order count" : "SUM `" + denominator + "`") + ".",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      }
    ];
  }

  function genericCombinationBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(combinations?|combo|together|\+)\b/i.test(q)) return [];
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    var quantity = quantityColumn(metadata);
    var groupCols = promptGroupColumns(metadata, q, "");
    groupCols = groupCols.filter(function (column) { return (metadata.categoricalColumns || []).indexOf(column) !== -1; });
    if (groupCols.length < 2) return [];
    var requestedMeasures = requestedNumericMeasures(metadata, q).filter(function (measure) {
      return groupCols.indexOf(measure.column) === -1;
    });
    if (!requestedMeasures.length) {
      if (revenue) addMeasure(requestedMeasures, revenue, "sum");
      if (profit && /\bprofit|margin\b/i.test(q)) addMeasure(requestedMeasures, profit, "sum");
      if (quantity && /\bunits?\s+sold|quantity\b/i.test(q)) addMeasure(requestedMeasures, quantity, "sum");
    }
    var sortMeasure = requestedMeasures.filter(function (measure) {
      return measure.mode === "sum" && (measure.column === revenue || /\btotal|bill|revenue|sales\b/i.test(measure.column));
    })[0] || requestedMeasures.filter(function (measure) { return measure.mode === "sum"; })[0];
    var sortMetric = sortMeasure ? measureFormula(sortMeasure) : (/\bprofit\s+margin|margin\b/i.test(q) && revenue && profit ? "Profit Margin %" : (/\bprofit\b/i.test(q) && profit ? "Profit" : (/\bunits?\s+sold|quantity\b/i.test(q) && quantity ? "Units Sold" : "Revenue")));
    if (!requestedMeasures.length && sortMetric === "Revenue" && !revenue) return [];
    var grouped = groupedByColumns(rows, groupCols);
    var items = grouped.order.map(function (key) {
      var item = grouped.grouped[key];
      var revenueValue = revenue ? aggregateRows(item.rows, revenue, "sum") : 0;
      var profitValue = profit ? aggregateRows(item.rows, profit, "sum") : 0;
      var quantityValue = quantity ? aggregateRows(item.rows, quantity, "sum") : 0;
      var out = { parts: item.parts, revenue: revenueValue, profit: profitValue, quantity: quantityValue, count: item.count };
      out.measureValues = requestedMeasures.map(function (measure) {
        return aggregateRows(item.rows, measure.column, measure.mode === "avg" ? "average" : "sum");
      });
      out.rpu = quantityValue ? revenueValue / quantityValue : 0;
      out.margin = revenueValue ? profitValue / revenueValue * 100 : 0;
      return out;
    });
    var bottom = /\bbottom|lowest|least\b/i.test(q);
    var metricGetter = function (item) {
      if (sortMeasure) {
        var index = requestedMeasures.indexOf(sortMeasure);
        return index >= 0 ? item.measureValues[index] : 0;
      }
      if (sortMetric === "Profit Margin %") return item.margin;
      if (sortMetric === "Profit") return item.profit;
      if (sortMetric === "Units Sold") return item.quantity;
      return item.revenue;
    };
    items.sort(function (a, b) { return (bottom ? metricGetter(a) - metricGetter(b) : metricGetter(b) - metricGetter(a)) || b.revenue - a.revenue; });
    items = items.slice(0, requestedLimit(q, 5));
    var countLabel = /\bencounter/i.test(q) ? "Encounter Count" : "Order Count";
    var measureHeaders = requestedMeasures.map(function (measure) { return measureFormula(measure); });
    var tableRows = [groupCols.concat([countLabel]).concat(measureHeaders.length ? measureHeaders : ["Revenue", "Profit", "Units Sold", "Average Revenue per Unit", "Profit Margin %"])].concat(items.map(function (item) {
      if (measureHeaders.length) return item.parts.concat([String(item.count)]).concat(item.measureValues.map(function (value) { return String(chartNumber(value)); }));
      return item.parts.concat([String(item.count), String(chartNumber(item.revenue)), String(chartNumber(item.profit)), String(chartNumber(item.quantity)), String(chartNumber(item.rpu)), chartNumber(item.margin) + "%"]);
    }));
    return [
      { type: "paragraph", data: { text: "Computed " + (bottom ? "bottom" : "top") + " " + items.length + " combinations from " + rows.length + " parsed workbook rows grouped by `" + groupCols.join("` + `") + "`. Formula: " + (measureHeaders.length ? measureHeaders.join("; ") : "sum requested metrics") + " per combination and sort by " + sortMetric + "." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: "bar",
          title: (bottom ? "Bottom " : "Top ") + items.length + " Combinations by " + sortMetric,
          labels: items.map(function (item) { return item.parts.join(" / "); }).join(","),
          values: items.map(function (item) { return chartNumber(metricGetter(item)); }).join(","),
          caption: "Generated from exact multi-column groups, not a single-category fallback.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed"
        }
      }
    ];
  }

  function genericContributionShareBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(share|contribution)\b/i.test(q) || !/\b(vs|versus|side\s+by\s+side|lower\s+than)\b/i.test(q)) return [];
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    var quantity = quantityColumn(metadata);
    var metrics = [];
    if (/\brevenue\b/i.test(q) && revenue) metrics.push({ column: revenue, label: "Revenue" });
    if (/\bprofit\b/i.test(q) && profit) metrics.push({ column: profit, label: "Profit" });
    if (/\bunits?\s+sold|quantity\b/i.test(q) && quantity) metrics.push({ column: quantity, label: "Units Sold" });
    if (metrics.length < 2) return [];
    var group = /\bmanufacturer\b/i.test(q) ? manufacturerColumn(metadata) : (/\bpayment\s*method\b/i.test(q) ? paymentColumn(metadata) : (/\bregion\b/i.test(q) ? regionColumn(metadata) : (/\bdrug|product\b/i.test(q) ? drugColumn(metadata) : resolveGroupColumn(metadata, q))));
    if (!group) return [];
    var grouped = {};
    var order = [];
    rows.forEach(function (row) {
      var label = String(rowValue(row, group) || "Blank").trim() || "Blank";
      if (!grouped[label]) {
        grouped[label] = { rows: 0 };
        metrics.forEach(function (metric) { grouped[label][metric.label] = 0; });
        order.push(label);
      }
      metrics.forEach(function (metric) { grouped[label][metric.label] += rowNumber(row, metric.column); });
      grouped[label].rows += 1;
    });
    var totals = {};
    metrics.forEach(function (metric) {
      totals[metric.label] = order.reduce(function (sum, label) { return sum + grouped[label][metric.label]; }, 0);
    });
    var first = metrics[0].label;
    var second = metrics[1].label;
    var rowsOut = order.map(function (label) {
      var aShare = totals[first] ? grouped[label][first] * 100 / totals[first] : 0;
      var bShare = totals[second] ? grouped[label][second] * 100 / totals[second] : 0;
      return { label: label, aShare: aShare, bShare: bShare, gap: aShare - bShare };
    });
    if (/\blower\s+than\b/i.test(q)) rowsOut = rowsOut.filter(function (item) { return item.aShare < item.bShare; });
    rowsOut.sort(function (a, b) { return Math.abs(b.gap) - Math.abs(a.gap) || a.label.localeCompare(b.label); });
    var tableRows = [[group].concat(metrics.map(function (metric) { return metric.label; })).concat(metrics.map(function (metric) { return metric.label + " Contribution %"; })).concat(["Gap"])].concat(rowsOut.map(function (item) {
      return [item.label].concat(metrics.map(function (metric) { return String(chartNumber(grouped[item.label][metric.label])); })).concat([chartNumber(item.aShare) + "%", chartNumber(item.bShare) + "%", chartNumber(item.gap) + "%"]);
    }));
    return [
      { type: "paragraph", data: { text: "Compared " + metrics.map(function (metric) { return metric.label + " contribution"; }).join(" vs ") + " by `" + group + "` from " + rows.length + " parsed workbook rows. Formula: contribution % = group metric / total metric x 100." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: first + " Contribution % vs " + second + " Contribution %",
          labels: rowsOut.map(function (item) { return item.label; }).join(","),
          values: rowsOut.map(function (item) { return chartNumber(item.aShare); }).join(",") + ";" + rowsOut.map(function (item) { return chartNumber(item.bShare); }).join(","),
          seriesLabels: first + " Contribution %," + second + " Contribution %",
          caption: "Side-by-side contribution percentages use independent totals for each metric.",
          color: "#2563eb",
          colors: "#2563eb,#dc2626"
        }
      }
    ];
  }

  function genericRankMismatchBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(high|strong|rank|mismatch|low)\b/i.test(q) || !/\b(low|weaker|rank)\b/i.test(q)) return [];
    var group = /\bdrug|product\b/i.test(q) ? drugColumn(metadata) : resolveGroupColumn(metadata, q);
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    var quantity = quantityColumn(metadata);
    if (!group) return [];
    var highMetric = /\bunits?\s+sold|quantity\b/i.test(q) && quantity ? "Units Sold" : (revenue ? "Revenue" : "");
    var lowMetric = /\brevenue\s+per\s+unit|per\s+unit\b/i.test(q) && revenue && quantity ? "Revenue per Unit" : (/\bprofit\s+margin|margin\b/i.test(q) && revenue && profit ? "Profit Margin %" : "");
    if (!highMetric || !lowMetric) return [];
    var grouped = {};
    var order = [];
    rows.forEach(function (row) {
      var label = String(rowValue(row, group) || "Blank").trim() || "Blank";
      if (!grouped[label]) grouped[label] = { revenue: 0, profit: 0, quantity: 0, rows: 0 };
      grouped[label].revenue += revenue ? rowNumber(row, revenue) : 0;
      grouped[label].profit += profit ? rowNumber(row, profit) : 0;
      grouped[label].quantity += quantity ? rowNumber(row, quantity) : 0;
      grouped[label].rows += 1;
      if (order.indexOf(label) === -1) order.push(label);
    });
    var items = order.map(function (label) {
      var item = grouped[label];
      return {
        label: label,
        revenue: item.revenue,
        profit: item.profit,
        quantity: item.quantity,
        count: item.rows,
        high: highMetric === "Units Sold" ? item.quantity : item.revenue,
        low: lowMetric === "Revenue per Unit" ? (item.quantity ? item.revenue / item.quantity : 0) : (item.revenue ? item.profit / item.revenue * 100 : 0)
      };
    });
    items.slice().sort(function (a, b) { return b.high - a.high; }).forEach(function (item, index) { item.highRank = index + 1; });
    items.slice().sort(function (a, b) { return b.low - a.low; }).forEach(function (item, index) { item.lowRank = index + 1; });
    items.sort(function (a, b) { return (b.lowRank - b.highRank) - (a.lowRank - a.highRank) || b.high - a.high; });
    items = items.slice(0, requestedLimit(q, 10));
    return [
      { type: "paragraph", data: { text: "Found `" + group + "` values where " + highMetric + " ranks high but " + lowMetric + " ranks weaker from " + rows.length + " parsed workbook rows. Formula: rank " + highMetric + " descending, rank " + lowMetric + " descending, mismatch = low-rank - high-rank." } },
      { type: "table", data: { withHeadings: true, rows: [[group, "Revenue", "Profit", "Units Sold", lowMetric, highMetric + " Rank", lowMetric + " Rank", "Mismatch"]].concat(items.map(function (item) {
        return [item.label, String(chartNumber(item.revenue)), String(chartNumber(item.profit)), String(chartNumber(item.quantity)), lowMetric === "Profit Margin %" ? chartNumber(item.low) + "%" : String(chartNumber(item.low)), String(item.highRank), String(item.lowRank), "High rank " + item.highRank + ", weak rank " + item.lowRank];
      })) } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: highMetric + " Rank vs " + lowMetric + " Rank",
          labels: items.map(function (item) { return item.label; }).join(","),
          values: items.map(function (item) { return item.highRank; }).join(",") + ";" + items.map(function (item) { return item.lowRank; }).join(","),
          seriesLabels: highMetric + " Rank," + lowMetric + " Rank",
          caption: "Lower rank number is better; larger gap means the requested mismatch is stronger.",
          color: "#2563eb",
          colors: "#2563eb,#dc2626"
        }
      }
    ];
  }

  function genericOutlierBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(outlier|unusually|anomal|top\s+\d+|highest|largest)\b/i.test(q) || !/\bcompared\s+with|compared\s+to|relative\s+to|vs\b/i.test(q)) return [];
    var revenue = revenueColumn(metadata);
    var quantity = quantityColumn(metadata);
    var discount = discountColumn(metadata);
    var profit = profitColumn(metadata);
    var x = /\brevenue\b/i.test(q) ? revenue : (/\bdiscount\b/i.test(q) ? discount : "");
    var y = /\bunits?\s+sold|quantity\b/i.test(q) ? quantity : (/\bprofit\b/i.test(q) ? profit : "");
    if (!x || !y) return [];
    var limit = requestedLimit(q, 20);
    var requestedColumns = (metadata.headings || []).filter(function (heading) { return promptMentionsColumn(q, heading); });
    [drugColumn(metadata), manufacturerColumn(metadata), regionColumn(metadata), customerTypeColumn(metadata), categoryColumn(metadata), cityColumn(metadata), x, y, profit, revenue, discount, quantity].forEach(function (column) {
      if (column && requestedColumns.indexOf(column) === -1) requestedColumns.push(column);
    });
    var sorted = rows.map(function (row) {
      var xv = rowNumber(row, x);
      var yv = rowNumber(row, y);
      return { row: row, x: xv, y: yv, ratio: yv ? xv / yv : xv };
    }).sort(function (a, b) { return b.ratio - a.ratio || b.x - a.x; }).slice(0, limit);
    var headers = ["Rank"].concat(requestedColumns).concat([x + " per " + y]);
    var tableRows = [headers].concat(sorted.map(function (item, index) {
      return [String(index + 1)].concat(requestedColumns.map(function (column) {
        return Object.prototype.hasOwnProperty.call((metadata.numericStats || {}), column) ? String(chartNumber(rowNumber(item.row, column))) : String(rowValue(item.row, column) || "");
      })).concat([String(chartNumber(item.ratio))]);
    }));
    return [
      { type: "paragraph", data: { text: "Identified top " + limit + " row-level outliers where `" + x + "` is unusually high compared with `" + y + "` from " + rows.length + " parsed workbook rows. Formula: score = `" + x + "` / `" + y + "`; sort score descending." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: "bar",
          title: "Top " + limit + " Outliers by " + x + " per " + y,
          labels: sorted.map(function (_, index) { return "Row " + (index + 1); }).join(","),
          values: sorted.map(function (item) { return chartNumber(item.ratio); }).join(","),
          caption: "Row-level output is used because the prompt asked for outlier sales/orders.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed"
        }
      }
    ];
  }

  function genericBucketMonthlyBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\bvs\s+non[-\s]|\bnon[-\s]\w+/i.test(q) || !asksForTimeBreakdown(q)) return [];
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "sale date", "month"]);
    if (!dateColumn) return [];
    var bucketValue = "";
    var bucketColumn = "";
    (metadata.categoricalColumns || []).some(function (column) {
      return uniqueColumnValues(metadata, column, 200).some(function (value) {
        if (value && new RegExp("\\b" + value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\b", "i").test(q)) {
          bucketValue = value;
          bucketColumn = column;
          return true;
        }
        return false;
      });
    });
    if (!bucketColumn || !bucketValue) return [];
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    var quantity = quantityColumn(metadata);
    var metrics = [];
    if (/\brevenue\b/i.test(q) && revenue) metrics.push(revenue);
    if (/\bprofit\b/i.test(q) && profit) metrics.push(profit);
    if (/\bunits?\s+sold|quantity\b/i.test(q) && quantity) metrics.push(quantity);
    if (!metrics.length && revenue) metrics.push(revenue);
    var months = {};
    var order = [];
    rows.forEach(function (row) {
      var bucket = monthBucket(rowValue(row, dateColumn));
      if (!bucket) return;
      if (!months[bucket.label]) {
        months[bucket.label] = { key: bucket.key, rows: 0 };
        metrics.forEach(function (metric) {
          months[bucket.label][bucketValue + " " + metric] = 0;
          months[bucket.label]["Non-" + bucketValue + " " + metric] = 0;
        });
        order.push(bucket.label);
      }
      var isBucket = columnKey(rowValue(row, bucketColumn)) === columnKey(bucketValue);
      metrics.forEach(function (metric) {
        months[bucket.label][(isBucket ? bucketValue : "Non-" + bucketValue) + " " + metric] += rowNumber(row, metric);
      });
      months[bucket.label].rows += 1;
    });
    order.sort(function (a, b) { return months[a].key - months[b].key; });
    var metricLabels = [];
    metrics.forEach(function (metric) {
      metricLabels.push(bucketValue + " " + metric);
      metricLabels.push("Non-" + bucketValue + " " + metric);
    });
    var tableRows = [["Month"].concat(metricLabels).concat(["Rows"])].concat(order.map(function (month) {
      return [month].concat(metricLabels.map(function (label) { return String(chartNumber(months[month][label] || 0)); })).concat([String(months[month].rows)]);
    }));
    var values = metricLabels.map(function (label) {
      return order.map(function (month) { return chartNumber(months[month][label] || 0); }).join(",");
    }).join(";");
    var blocks = [
      { type: "paragraph", data: { text: "Compared `" + bucketValue + "` vs `Non-" + bucketValue + "` by month from " + rows.length + " parsed workbook rows. Formula: group by month and bucket from `" + bucketColumn + "`, then sum requested metrics." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } }
    ];
    if (metrics.length > 1) {
      metrics.forEach(function (metric, index) {
        var labels = [bucketValue + " " + metric, "Non-" + bucketValue + " " + metric];
        blocks.push({
          type: "chart",
          data: {
            type: "comparison",
            title: bucketValue + " vs Non-" + bucketValue + " Monthly " + metric,
            labels: order.join(","),
            values: labels.map(function (label) {
              return order.map(function (month) { return chartNumber(months[month][label] || 0); }).join(",");
            }).join(";"),
            seriesLabels: labels.join(","),
            caption: "Chart keeps one metric scale for readability. `" + metric + "` is summed monthly for `" + bucketValue + "` and `Non-" + bucketValue + "`.",
            color: index ? "#f59e0b" : "#2563eb",
            colors: index ? "#f59e0b,#dc2626" : "#2563eb,#16a34a"
          }
        });
      });
      return blocks;
    }
    blocks.push({
      type: "chart",
      data: {
        type: "comparison",
        title: bucketValue + " vs Non-" + bucketValue + " Monthly Performance",
        labels: order.join(","),
        values: values,
        seriesLabels: metricLabels.join(","),
        caption: "Series are bucketed from `" + bucketColumn + "` and metrics are monthly totals.",
        color: "#2563eb",
        colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
      }
    });
    return blocks;
  }

  function genericPeriodConditionBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(increased|decreased|increase|decrease|quarter[-\s]over[-\s]quarter|qoq|previous)\b/i.test(q) || !/\brevenue\b/i.test(q) || !/\bunits?\s+sold|quantity\b/i.test(q)) return [];
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "sale date", "month"]);
    var revenue = revenueColumn(metadata);
    var quantity = quantityColumn(metadata);
    var group = /\bdrug|product\b/i.test(q) ? drugColumn(metadata) : resolveGroupColumn(metadata, q);
    if (!dateColumn || !revenue || !quantity || !group) return [];
    var useQuarter = /\bquarter|qoq\b/i.test(q);
    var grouped = {};
    rows.forEach(function (row) {
      var period = useQuarter ? quarterBucket(rowValue(row, dateColumn)) : monthBucket(rowValue(row, dateColumn));
      if (!period) return;
      var label = String(rowValue(row, group) || "Blank").trim() || "Blank";
      if (!grouped[label]) grouped[label] = {};
      if (!grouped[label][period.label]) grouped[label][period.label] = { key: period.key, revenue: 0, quantity: 0, count: 0 };
      grouped[label][period.label].revenue += rowNumber(row, revenue);
      grouped[label][period.label].quantity += rowNumber(row, quantity);
      grouped[label][period.label].count += 1;
    });
    var matches = [];
    Object.keys(grouped).forEach(function (label) {
      var periods = Object.keys(grouped[label]).sort(function (a, b) { return grouped[label][a].key - grouped[label][b].key; });
      for (var i = 1; i < periods.length; i += 1) {
        var current = grouped[label][periods[i]];
        var previous = grouped[label][periods[i - 1]];
        var revenueChange = current.revenue - previous.revenue;
        var quantityChange = current.quantity - previous.quantity;
        if (revenueChange > 0 && quantityChange < 0) {
          matches.push({ label: label, period: periods[i], previous: periods[i - 1], revenueChange: revenueChange, quantityChange: quantityChange, revenue: current.revenue, quantity: current.quantity });
        }
      }
    });
    matches.sort(function (a, b) { return b.revenueChange - a.revenueChange || a.label.localeCompare(b.label); });
    matches = matches.slice(0, requestedLimit(q, 20));
    if (!matches.length) matches.push({ label: "No matching group", period: "", previous: "", revenueChange: 0, quantityChange: 0, revenue: 0, quantity: 0 });
    return [
      { type: "paragraph", data: { text: "Checked " + (useQuarter ? "quarter-over-quarter" : "month-over-month") + " changes by `" + group + "` from " + rows.length + " parsed workbook rows. Formula: compare current period SUM Revenue and SUM `" + quantity + "` with previous period." } },
      { type: "table", data: { withHeadings: true, rows: [[group, "Period", "Previous Period", "Revenue Change", "Units Sold Change", "Revenue", "Units Sold"]].concat(matches.map(function (item) {
        return [item.label, item.period, item.previous, String(chartNumber(item.revenueChange)), String(chartNumber(item.quantityChange)), String(chartNumber(item.revenue)), String(chartNumber(item.quantity))];
      })) } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: "Revenue Increase vs Units Decrease",
          labels: matches.map(function (item) { return item.label + " " + item.period; }).join(","),
          values: matches.map(function (item) { return chartNumber(item.revenueChange); }).join(",") + ";" + matches.map(function (item) { return chartNumber(item.quantityChange); }).join(","),
          seriesLabels: "Revenue Change,Units Sold Change",
          caption: "Rows where revenue increased while units sold decreased are returned.",
          color: "#2563eb",
          colors: "#2563eb,#dc2626"
        }
      }
    ];
  }

  function genericTimeSeriesByGroupBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !asksForTimeBreakdown(q)) return [];
    if (/\bnot\s+by\s+(?:month|date|time)\b/i.test(q)) return [];
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["sale date", "date", "month", "created_at", "updated_at"]);
    if (!dateColumn) return [];
    var groupColumn = "";
    if (/\bcustomer\s+type\b/i.test(q)) groupColumn = customerTypeColumn(metadata);
    else if (/\bdosage\s+form\b/i.test(q)) groupColumn = resolveNamedColumn(metadata, ["dosage form", "form"], "category");
    else if (/\bdrug\s+name|\bdrug\b/i.test(q)) groupColumn = drugColumn(metadata);
    else if (/\bmanufacturer\b/i.test(q)) groupColumn = manufacturerColumn(metadata);
    else if (/\bregion\b/i.test(q)) groupColumn = regionColumn(metadata);
    else groupColumn = resolveGroupColumn(metadata, q);
    if (!groupColumn || groupColumn === dateColumn || isForbiddenColumn(metadata, q, groupColumn)) return [];
    var metric = "";
    if (/\brevenue\b/i.test(q)) metric = revenueColumn(metadata);
    else if (/\bprofit\b/i.test(q)) metric = profitColumn(metadata);
    else if (/\bunits?\s+sold|quantity|qty\b/i.test(q)) metric = quantityColumn(metadata);
    else if (/\bdiscount\b/i.test(q)) metric = discountColumn(metadata);
    else metric = manualMetricColumn(metadata, q);
    if (!metric) return [];
    var useQuarter = /\bquarter|quarterly\b/i.test(q);
    if (!useQuarter && /\btoo\s+many\s+months|summari[sz]e\s+by\s+quarter\b/i.test(q)) {
      var seenMonths = {};
      rows.forEach(function (row) {
        var month = monthBucket(rowValue(row, dateColumn));
        if (month) seenMonths[month.label] = true;
      });
      useQuarter = Object.keys(seenMonths).length > 12;
    }
    var bucketFn = useQuarter ? quarterBucket : monthBucket;
    var periodLabel = useQuarter ? "Quarter" : "Month";
    var groups = {};
    var periodOrder = [];
    var groupOrder = [];
    rows.forEach(function (row) {
      var bucket = bucketFn(rowValue(row, dateColumn));
      if (!bucket) return;
      var period = bucket.label;
      var label = String(rowValue(row, groupColumn) || "Blank").trim() || "Blank";
      if (!groups[period]) {
        groups[period] = { key: bucket.key, values: {}, rows: 0 };
        periodOrder.push(period);
      }
      if (groupOrder.indexOf(label) < 0) groupOrder.push(label);
      if (!groups[period].values[label]) groups[period].values[label] = { metric: 0, rows: 0 };
      groups[period].values[label].metric += rowNumber(row, metric);
      groups[period].values[label].rows += 1;
      groups[period].rows += 1;
    });
    periodOrder.sort(function (a, b) { return groups[a].key - groups[b].key; });
    groupOrder.sort(function (a, b) {
      var av = periodOrder.reduce(function (sum, period) { return sum + ((groups[period].values[a] || {}).metric || 0); }, 0);
      var bv = periodOrder.reduce(function (sum, period) { return sum + ((groups[period].values[b] || {}).metric || 0); }, 0);
      return bv - av || a.localeCompare(b);
    });
    groupOrder = groupOrder.slice(0, 8);
    var tableRows = [[periodLabel, groupColumn, metric, "Rows"]];
    periodOrder.forEach(function (period) {
      groupOrder.forEach(function (label) {
        var cell = groups[period].values[label] || { metric: 0, rows: 0 };
        tableRows.push([period, label, String(chartNumber(cell.metric)), String(cell.rows)]);
      });
    });
    var chartValues = groupOrder.map(function (label) {
      return periodOrder.map(function (period) {
        return chartNumber(((groups[period].values[label] || {}).metric) || 0);
      }).join(",");
    }).join(";");
    var requested = requestedChartType(q);
    var chartType = requested === "line" ? "line" : requested === "comparison" ? "comparison" : requested === "bar" ? "comparison" : "stackedBar";
    return [
      { type: "paragraph", data: { text: "Computed " + periodLabel.toLowerCase() + "ly `" + metric + "` by `" + groupColumn + "` from " + rows.length + " parsed workbook rows. Formula: GROUP BY " + periodLabel + " + `" + groupColumn + "`, SUM `" + metric + "`." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: chartType,
          title: periodLabel + "ly " + metric + " by " + groupColumn,
          labels: periodOrder.join(","),
          values: chartValues,
          seriesLabels: groupOrder.join(","),
          caption: "Chart keeps `" + groupColumn + "` as separate series and uses " + periodLabel.toLowerCase() + "-level totals only.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2,#db2777,#65a30d"
        }
      }
    ];
  }

  function manualGroupedRateSpec(metadata, question) {
    var q = String(question || "");
    if (!/\b(rate|percentage|percent|%)\b/i.test(q)) return null;
    function spec(label, column, pattern, formula) {
      if (!column) return null;
      return {
        label: label,
        column: column,
        positive: function (row) { return pattern.test(String(rowValue(row, column) || "").trim()); },
        formula: formula
      };
    }
    if (/\breadmission|readmitted\b/i.test(q)) {
      var readmission = findColumnByWords(metadata, ["readmission", "readmitted", "readmission30days", "readmission 30 days", "isreadmitted"]);
      return readmission ? { label: "Readmission Rate %", column: readmission, positive: function (row) { return truthySheetValue(rowValue(row, readmission)); }, formula: "readmitted rows / group row count x 100" } : null;
    }
    if (/\bicu\b/i.test(q)) {
      var icu = findColumnByWords(metadata, ["icu", "icuflag", "icu flag", "isicu"]);
      return icu ? { label: "ICU Rate %", column: icu, positive: function (row) { return truthySheetValue(rowValue(row, icu)); }, formula: "ICU rows / group row count x 100" } : null;
    }
    if (/\bmortality|death|deceased\b/i.test(q)) return spec("Mortality Rate %", findColumnByWords(metadata, ["outcome", "status", "patient outcome"]), /^(deceased|dead|death|died|yes|true|1)$/i, "deceased rows / group row count x 100");
    if (/\bclaim\b/i.test(q) && /\breject|rejection\b/i.test(q)) return spec("Claim Rejection Rate %", findColumnByWords(metadata, ["claim status", "claimstatus", "status"]), /^(rejected|reject|denied)$/i, "rejected claim rows / group row count x 100");
    if (/\bclaim\b/i.test(q) && /\bapproval|approved|approve\b/i.test(q)) return spec("Claim Approval Rate %", findColumnByWords(metadata, ["claim status", "claimstatus", "status"]), /^(approved|approve|paid|accepted)$/i, "approved claim rows / group row count x 100");
    if (/\brecall|recalled\b/i.test(q)) return spec("Recall Rate %", findColumnByWords(metadata, ["recall", "recalled", "recallflag", "recall flag"]), /^(yes|true|1|recalled)$/i, "recalled rows / group row count x 100");
    if (/\bqa\b/i.test(q) && /\bfail|failure|failed\b/i.test(q)) return spec("QA Failure Rate %", findColumnByWords(metadata, ["qa status", "qastatus", "quality status", "status"]), /^(failed|fail|rejected|reject)$/i, "failed QA rows / group row count x 100");
    if (/\battrition|terminated|left|resigned\b/i.test(q)) return spec("Attrition Rate %", findColumnByWords(metadata, ["attrition", "attrition flag", "left company", "resigned", "termination status"]), /^(yes|true|1|left|resigned|terminated|inactive)$/i, "attrition rows / group row count x 100");
    if (/\blate|delay|delayed|overdue\b/i.test(q)) return spec("Late Delivery Rate %", findColumnByWords(metadata, ["delivery status", "shipment status", "status", "late", "delayed"]), /^(yes|true|1|late|delayed|overdue|missed)$/i, "late/delayed rows / group row count x 100");
    if (/\bsla|breach|breached\b/i.test(q)) return spec("SLA Breach Rate %", findColumnByWords(metadata, ["sla", "sla breached", "sla breach", "status"]), /^(yes|true|1|breached|missed|failed)$/i, "SLA breach rows / group row count x 100");
    if (/\bresolved|closed|completed|complete|completion\b/i.test(q)) return spec("Completion Rate %", findColumnByWords(metadata, ["task status", "project status", "ticket status", "case status", "status"]), /^(yes|true|1|resolved|closed|completed|complete|done|won)$/i, "completed/resolved rows / group row count x 100");
    if (/\binspection\b/i.test(q) && /\bfail|failure|failed\b/i.test(q)) return spec("Inspection Failure Rate %", findColumnByWords(metadata, ["inspection status", "quality status", "pass fail", "result", "status"]), /^(failed|fail|rejected|reject|no|false|0)$/i, "failed inspection rows / group row count x 100");
    if (/\bcancel|cancellation|cancelled|canceled\b/i.test(q)) return spec("Cancellation Rate %", findColumnByWords(metadata, ["cancellation", "cancelled", "canceled", "booking status", "reservation status", "status"]), /^(yes|true|1|cancelled|canceled|cancel)$/i, "cancelled rows / group row count x 100");
    if (/\boccupancy|occupied\b/i.test(q)) {
      var occupancy = findColumnByWords(metadata, ["occupancy", "occupied", "occupancy rate", "status"]);
      if (occupancy && (metadata.numericStats || {})[occupancy]) return null;
      return spec("Occupancy Rate %", occupancy, /^(yes|true|1|occupied|booked|active)$/i, "occupied rows / group row count x 100");
    }
    if (/\bfraud\b/i.test(q)) return spec("Fraud Rate %", findColumnByWords(metadata, ["fraud", "fraud flag", "is fraud"]), /^(yes|true|1|fraud|fraudulent)$/i, "fraud rows / group row count x 100");
    if (/\bdelinquency|delinquent\b/i.test(q)) return spec("Delinquency Rate %", findColumnByWords(metadata, ["delinquency", "delinquent", "loan status", "status"]), /^(yes|true|1|delinquent|overdue|default|defaulted)$/i, "delinquent rows / group row count x 100");
    if (/\bchurn|churned\b/i.test(q)) return spec("Churn Rate %", findColumnByWords(metadata, ["churn", "churn flag", "churn status", "cancelled", "status"]), /^(yes|true|1|churned|cancelled|canceled|inactive)$/i, "churned rows / group row count x 100");
    if (/\bactive\s+user|active\b/i.test(q)) return spec("Active Rate %", findColumnByWords(metadata, ["active", "active user", "active flag", "status"]), /^(yes|true|1|active)$/i, "active rows / group row count x 100");
    if (/\bwin|won\b/i.test(q)) return spec("Win Rate %", findColumnByWords(metadata, ["deal status", "opportunity status", "pipeline stage", "stage", "status"]), /^(won|win|closed won|yes|true|1)$/i, "won rows / group row count x 100");
    if (/\bpass|passed|success|successful|approved\b/i.test(q)) return spec("Pass Rate %", findColumnByWords(metadata, ["pass status", "result", "status"]), /^(yes|true|1|passed|pass|success|successful|approved)$/i, "passed/successful rows / group row count x 100");
    var groupColumn = resolveGroupColumn(metadata, q);
    var mentionedStatus = ((metadata.headings || []).concat(metadata.categoricalColumns || [])).filter(function (column, index, list) {
      return list.indexOf(column) === index && column !== groupColumn && promptMentionsColumn(q, column) && !(metadata.numericStats || {})[column] && !semanticColumnInfo(metadata, column).identifier;
    })[0];
    if (mentionedStatus && /\byes\b/i.test(q)) {
      return {
        label: columnLabel(mentionedStatus) + " Yes Rate %",
        column: mentionedStatus,
        positive: function (row) { return truthySheetValue(rowValue(row, mentionedStatus)); },
        formula: "`" + mentionedStatus + "` Yes rows / group row count x 100"
      };
    }
    if (mentionedStatus) {
      return {
        label: columnLabel(mentionedStatus) + " Positive Rate %",
        column: mentionedStatus,
        positive: function (row) {
          var value = String(rowValue(row, mentionedStatus) || "").trim();
          return /^(yes|true|1|approved|passed|pass|success|successful|completed|complete|active|won|accepted|paid)$/i.test(value);
        },
        formula: "positive `" + mentionedStatus + "` rows / group row count x 100"
      };
    }
    return null;
  }

  function binaryRateComparisonColumn(metadata, question, rateColumn) {
    var q = String(question || "");
    var candidates = [];
    if (/\bicu|non[-\s]?icu\b/i.test(q)) candidates.push(findColumnByWords(metadata, ["icu", "icuflag", "icu flag", "isicu"]));
    if (/\breadmission|readmitted|non[-\s]?readmitted\b/i.test(q)) candidates.push(findColumnByWords(metadata, ["readmission", "readmitted", "readmission30days", "readmission 30 days", "isreadmitted"]));
    if (/\brecall|recalled|non[-\s]?recalled\b/i.test(q)) candidates.push(findColumnByWords(metadata, ["recall", "recalled", "recallflag", "recall flag"]));
    if (/\bchurn|churned|non[-\s]?churned\b/i.test(q)) candidates.push(findColumnByWords(metadata, ["churn", "churn flag", "churn status", "cancelled", "status"]));
    if (/\bactive|inactive|non[-\s]?active\b/i.test(q)) candidates.push(findColumnByWords(metadata, ["active", "active user", "active flag", "status"]));
    if (/\bfraud|non[-\s]?fraud\b/i.test(q)) candidates.push(findColumnByWords(metadata, ["fraud", "fraud flag", "is fraud"]));
    if (/\bcancel|cancellation|cancelled|canceled|non[-\s]?cancel/i.test(q)) candidates.push(findColumnByWords(metadata, ["cancellation", "cancelled", "canceled", "booking status", "reservation status", "status"]));
    candidates.push(resolveGroupColumn(metadata, q));
    for (var i = 0; i < candidates.length; i += 1) {
      var column = candidates[i];
      if (column && column !== rateColumn && !(metadata.numericStats || {})[column]) return column;
    }
    return "";
  }

  function binaryRateComparisonBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(rate|percentage|percent|%)\b/i.test(q) || !(/\bvs\b|\bversus\b|\bnon[-\s]?\w+/i.test(q))) return [];
    var rateSpec = manualGroupedRateSpec(metadata, q);
    if (!rateSpec || !rateSpec.column) return [];
    var flagColumn = binaryRateComparisonColumn(metadata, q, rateSpec.column);
    if (!flagColumn) return [];
    var positiveRows = [];
    var negativeRows = [];
    rows.forEach(function (row) {
      if (truthySheetValue(rowValue(row, flagColumn))) positiveRows.push(row);
      else negativeRows.push(row);
    });
    function rateFor(list) {
      var matching = list.filter(rateSpec.positive).length;
      return { matching: matching, total: list.length, rate: list.length ? matching * 100 / list.length : 0 };
    }
    var positive = rateFor(positiveRows);
    var negative = rateFor(negativeRows);
    var flagLabel = columnLabel(flagColumn);
    var metricLabel = rateSpec.label.replace(/\s*%$/, "");
    var positiveLabel = /\bicu\b/i.test(q) ? "ICU" : flagLabel + " = Yes";
    var negativeLabel = /\bicu\b/i.test(q) ? "Non-ICU" : flagLabel + " = No";
    var tableRows = [
      ["Group", "Matching Rows", "Rows Checked", metricLabel, "Formula"],
      [positiveLabel, String(positive.matching), String(positive.total), formatPercent(positive.rate), rateSpec.formula],
      [negativeLabel, String(negative.matching), String(negative.total), formatPercent(negative.rate), rateSpec.formula],
      ["All rows", String(positive.matching + negative.matching), String(rows.length), formatPercent(rows.length ? (positive.matching + negative.matching) * 100 / rows.length : 0), "matching rows / all rows x 100"]
    ];
    return [
      { type: "paragraph", data: { text: "Computed " + metricLabel + " for `" + flagColumn + "` vs non-`" + flagColumn + "` from " + rows.length + " parsed workbook rows. Formula: " + rateSpec.formula + "." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: metricLabel + " by " + flagLabel,
          labels: positiveLabel + "," + negativeLabel,
          values: chartNumber(positive.rate) + "," + chartNumber(negative.rate),
          seriesLabels: metricLabel,
          caption: "Generated from all parsed rows using `" + rateSpec.column + "` for the rate condition and `" + flagColumn + "` for the vs/non-vs comparison.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a"
        }
      }
    ];
  }

  function averageByFlagComparisonBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !(/\bvs\b|\bversus\b|\bnon[-\s]?\w+/i.test(q))) return [];
    if (!/\b(avg|average|mean|total|sum)\b/i.test(q)) return [];
    var metric = manualMetricColumn(metadata, q);
    if (!metric || !(metadata.numericStats || {})[metric]) return [];
    var flagColumn = binaryRateComparisonColumn(metadata, q, "");
    if (!flagColumn || (metadata.numericStats || {})[flagColumn]) return [];
    var mode = /\b(avg|average|mean)\b/i.test(q) ? "average" : "sum";
    var positiveRows = [];
    var negativeRows = [];
    rows.forEach(function (row) {
      if (truthySheetValue(rowValue(row, flagColumn))) positiveRows.push(row);
      else negativeRows.push(row);
    });
    function metricFor(list) {
      var values = list.map(function (row) { return numericValue(rowValue(row, metric)); }).filter(function (value) { return value !== null; });
      var sum = values.reduce(function (total, value) { return total + value; }, 0);
      return { value: mode === "average" ? (values.length ? sum / values.length : 0) : sum, numericRows: values.length, totalRows: list.length };
    }
    var positive = metricFor(positiveRows);
    var negative = metricFor(negativeRows);
    var flagLabel = columnLabel(flagColumn);
    var metricLabel = columnLabel(metric);
    var operation = mode === "average" ? "Average " + metricLabel : "Total " + metricLabel;
    var positiveLabel = /\bicu\b/i.test(q) ? "ICU" : (/\breadmitted?|readmission\b/i.test(q) ? "Readmitted" : flagLabel + " = Yes");
    var negativeLabel = /\bicu\b/i.test(q) ? "Non-ICU" : (/\breadmitted?|readmission\b/i.test(q) ? "Non-readmitted" : flagLabel + " = No");
    var formula = (mode === "average" ? "AVG" : "SUM") + " `" + metric + "` grouped by `" + flagColumn + "` true vs false";
    return [
      { type: "paragraph", data: { text: "Computed " + operation + " for `" + flagColumn + "` vs non-`" + flagColumn + "` from " + rows.length + " parsed workbook rows. Formula: " + formula + "." } },
      { type: "table", data: { withHeadings: true, rows: [["Group", operation, "Numeric Rows", "Rows Checked", "Formula"], [positiveLabel, chartNumber(positive.value), String(positive.numericRows), String(positive.totalRows), formula], [negativeLabel, chartNumber(negative.value), String(negative.numericRows), String(negative.totalRows), formula], ["All rows", chartNumber(metricFor(rows).value), String(metricFor(rows).numericRows), String(rows.length), (mode === "average" ? "AVG" : "SUM") + " `" + metric + "` over all parsed rows"]] } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: operation + " by " + flagLabel,
          labels: positiveLabel + "," + negativeLabel,
          values: chartNumber(positive.value) + "," + chartNumber(negative.value),
          seriesLabels: operation,
          caption: "Generated from all parsed rows using `" + flagColumn + "` as the true/non-true comparison flag.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a"
        }
      }
    ];
  }

  function ambiguousManualAnalyticsPrompt(metadata, question) {
    var q = String(question || "");
    if (/\bperformance\b/i.test(q) && executionRequestsGrouping(q) && !/\b(total|sum|average|avg|mean|median|min|max|count|rate|percentage|percent|share|revenue|profit|bill|cost|claim|payment|wait|stay|satisfaction|age)\b/i.test(q)) return true;
    if (/\bpredict|forecast|forecasting\b/i.test(q) && /\bdo\s+not\s+use\s+(?:a\s+)?forecast(?:ing)?(?:\s+method)?|without\s+forecast(?:ing)?|no\s+forecast(?:ing)?\b/i.test(q)) return true;
    if (identifierColumns(metadata || {}).some(function (column) { return promptMentionsIdentifierColumn(q, column); }) && executionRequestsGrouping(q) && /\bchart|graph|group(?:ed)?\s+by|group\s+results\s+by\b/i.test(q)) return true;
    if (/\bbest\b/i.test(q) && !/\b(by|using|based\s+on|highest|lowest|total|average|rate|share|cost|bill|revenue|profit|satisfaction|readmission|mortality)\b/i.test(q)) return true;
    return false;
  }

  function missingRequestedMetric(metadata, question) {
    var q = String(question || "");
    var qWithoutFilterValues = questionWithoutManualFilterValues(metadata || {}, q);
    var headings = (metadata || {}).headings || [];
    var headingKeys = {};
    headings.forEach(function (heading) { headingKeys[columnKey(heading)] = true; });
    var filters = manualFilters(metadata || {}, q);
    var filterValues = filters.map(function (filter) { return columnKey(filter.value); });
    var columnLike = q.match(/\b[A-Za-z][A-Za-z0-9]*(?:_[A-Za-z0-9]+)+\b/g) || [];
    for (var i = 0; i < columnLike.length; i += 1) {
      if (filterValues.indexOf(columnKey(columnLike[i])) >= 0) continue;
      if (!headingKeys[columnKey(columnLike[i])]) return columnLike[i];
    }
    function appearsOnlyAsFilterValue(term) {
      var key = columnKey(term);
      return filterValues.indexOf(key) >= 0 && !new RegExp("\\b(show|calculate|total|sum|average|avg|mean|median|min|max|rate|share)\\b[^.;,]{0,40}\\b" + escapeRegExp(term).replace(/\\ /g, "\\s+") + "\\b", "i").test(qWithoutFilterValues);
    }
    if (appearsOnlyAsFilterValue("medicine")) return "";
    if (appearsOnlyAsFilterValue("revenue")) return "";
    if (appearsOnlyAsFilterValue("profit")) return "";
    if (/\brevenue\b/i.test(qWithoutFilterValues) && !strictRevenueColumn(metadata || {})) return "Revenue";
    if (/\bprofit\b/i.test(qWithoutFilterValues) && !profitColumn(metadata || {})) return "Profit";
    if (/\bdiscount\b/i.test(qWithoutFilterValues) && !discountColumn(metadata || {})) return "Discount";
    return "";
  }

  function questionWithoutManualFilterValues(metadata, question) {
    var text = String(question || "");
    manualFilters(metadata || {}, text).forEach(function (filter) {
      var value = String(filter.value || "");
      if (!value) return;
      text = text.replace(new RegExp("=\\s*[`'\\\"]?" + escapeRegExp(value) + "[`'\\\"]?", "ig"), "= ");
    });
    return text;
  }

  function missingColumnGuardBlocks(metadata, question) {
    var q = String(question || "");
    if (/\bpredict|forecast|forecasting\b/i.test(q) && /\bdo\s+not\s+use\s+(?:a\s+)?forecast(?:ing)?(?:\s+method)?|without\s+forecast(?:ing)?|no\s+forecast(?:ing)?\b/i.test(q)) {
      return [
        { type: "paragraph", data: { text: "Instructions conflict; ask for clarification instead of fabricating a forecast." } },
        { type: "table", data: { withHeadings: true, rows: [["Issue", "Safe next step"], ["Prediction requested", "Use a forecasting method or ask for a historical trend."], ["Forecasting disallowed", "Ask for historical yearly totals instead."]] } }
      ];
    }
    var missing = missingRequestedMetric(metadata || {}, question) || "Requested metric";
    var headings = ((metadata || {}).headings || []).slice(0, 12).map(columnLabel).join(", ");
    return [
      { type: "paragraph", data: { text: missing + " column was not found in this sheet. " + missing + " does not exist in the uploaded dataset, and I did not fabricate it or substitute another column." } },
      { type: "table", data: { withHeadings: true, rows: [["Requested metric", "Status", "Available columns sample"], [missing, "Not found", headings || "No columns available"]] } }
    ];
  }

  function duplicateHeaderAmbiguityBlocks(metadata, question) {
    var q = String(question || "");
    if (!/\b(two|duplicate|same|identical)\s+(?:cols?|columns?|headers?)\b|\b(?:cols?|columns?)\s+both\s+named\b|\bsame\s+name\b/i.test(q)) return [];
    var headings = ((metadata || {}).headings || []).map(function (heading) { return String(heading || ""); });
    var counts = {};
    headings.forEach(function (heading) {
      var key = columnKey(heading);
      if (!key) return;
      counts[key] = (counts[key] || 0) + 1;
    });
    var duplicates = Object.keys(counts).filter(function (key) { return counts[key] > 1; });
    var requested = (q.match(/\bnamed\s+[`'"]?([A-Za-z][A-Za-z0-9_ -]+)[`'"]?/i) || [])[1] || "";
    if (!duplicates.length && !requested) return [];
    var rows = [["Issue", "Safe next step"]];
    if (requested) rows.push(["Requested ambiguous column", requested]);
    duplicates.slice(0, 8).forEach(function (key) {
      rows.push(["Duplicate header", headings.filter(function (heading) { return columnKey(heading) === key; }).join(" / ")]);
    });
    rows.push(["Clarification required", "Rename or choose the exact column before summing/counting so I do not guess incorrectly."]);
    return [
      { type: "paragraph", data: { text: "Duplicate header ambiguity detected. I need clarification before calculating because the prompt refers to columns that may share the same name." } },
      { type: "table", data: { withHeadings: true, rows: rows } }
    ];
  }

  function numericTextCoercionGuardBlocks(metadata, question) {
    var q = String(question || "");
    if (!/(?:₹|\$|€|£|\btext\b|['"]\d[\d,]*(?:\.\d+)?['"])/i.test(q) || !/\b(?:total|sum|aggregate|coerce|numeric|number)\b/i.test(q)) return [];
    var numericColumns = Object.keys((metadata || {}).numericStats || {}).filter(function (column) {
      return !semanticColumnInfo(metadata || {}, column).avoidDefault;
    }).slice(0, 8);
    return [
      { type: "paragraph", data: { text: "Coerce valid currency/numeric text before aggregation and flag invalid tokens. I will not silently mix invalid text tokens into a total." } },
      { type: "table", data: { withHeadings: true, rows: [["Guard", "Handling"], ["Currency text", "Strip currency symbols and thousands separators only when the remaining value is numeric."], ["Plain numeric text", "Treat values like 1200 or '1,200' as numeric after validation."], ["Invalid tokens", "Flag invalid tokens instead of fabricating a numeric value."], ["Available numeric columns", numericColumns.length ? numericColumns.join(", ") : "No numeric columns detected"]] } }
    ];
  }

  function clarificationBlocks(metadata, question) {
    var q = String(question || "");
    if (/\bpredict|forecast|forecasting\b/i.test(q) && /\bdo\s+not\s+use\s+(?:a\s+)?forecast(?:ing)?(?:\s+method)?|without\s+forecast(?:ing)?|no\s+forecast(?:ing)?\b/i.test(q)) {
      return [
        { type: "paragraph", data: { text: "This prompt is contradictory: it asks for a prediction but also says not to use forecasting. I cannot fabricate next year's result without a forecasting method, so please clarify whether you want a historical yearly summary or a forecast." } },
        { type: "table", data: { withHeadings: true, rows: [["Issue", "Safe next step"], ["Prediction requested", "Ask for a forecast and allow forecasting."], ["Forecasting disallowed", "Ask for historical yearly TotalBill_INR instead."]] } }
      ];
    }
    var mentionedIdentifier = identifierColumns(metadata || {}).filter(function (column) { return promptMentionsIdentifierColumn(q, column); })[0] || "";
    if (mentionedIdentifier && executionRequestsGrouping(q) && /\bchart|graph|group(?:ed)?\s+by|group\s+results\s+by\b/i.test(q)) {
      return [
        { type: "paragraph", data: { text: columnLabel(mentionedIdentifier) + " is a high-cardinality identifier. I avoided grouping or charting by it as a default because it would create an unreadable row-level chart. Ask for a specific identifier ranking or a category grouping if you really need it." } },
        { type: "table", data: { withHeadings: true, rows: [["Guard", "Recommendation"], ["Identifier grouping blocked", "Use Department, City, Outcome, ClaimStatus, or another categorical column for charts."], ["Explicit identifier analysis", "Ask for top/bottom " + columnLabel(mentionedIdentifier) + " by a specific metric if that is intended."]] } }
      ];
    }
    var groupColumn = resolveGroupColumn(metadata || {}, q);
    var metrics = Object.keys((metadata || {}).numericStats || {}).filter(function (column) { return !semanticColumnInfo(metadata || {}, column).avoidDefault; }).slice(0, 6);
    var examples = metrics.map(function (metric) {
      return groupColumn ? "Show average " + columnLabel(metric) + " by " + columnLabel(groupColumn) + "." : "Show total " + columnLabel(metric) + ".";
    });
    return [
      { type: "paragraph", data: { text: "I need a specific metric or criterion before I can calculate this. I will not invent a default metric for this prompt." } },
      { type: "table", data: { withHeadings: true, rows: [["Needed", "Examples"], ["Metric or criterion", examples.length ? examples.join(" | ") : "Ask for a specific total, average, count, rate, or share."], ["Grouping", groupColumn ? columnLabel(groupColumn) : "Not required unless you ask by a category"]] } }
    ];
  }

  function distributionStatsBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(q1|quartile|median|q3|iqr)\b/i.test(q)) return [];
    var metric = manualMetricColumn(metadata, q);
    if (!metric || !(metadata.numericStats || {})[metric]) return [];
    var values = rows.map(function (row) { return numericValue(rowValue(row, metric)); }).filter(function (value) { return value !== null; });
    if (!values.length) return [];
    var q1 = quantileNumber(values, 0.25);
    var median = quantileNumber(values, 0.5);
    var q3 = quantileNumber(values, 0.75);
    var iqr = q3 - q1;
    return [
      { type: "paragraph", data: { text: "Computed Q1, median, Q3, and IQR for `" + metric + "` from " + values.length + " non-missing numeric rows." } },
      { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Q1 " + columnLabel(metric), quantileDisplayValue(q1), "25th percentile of `" + metric + "`"], ["Median " + columnLabel(metric), quantileDisplayValue(median), "50th percentile of `" + metric + "`"], ["Q3 " + columnLabel(metric), quantileDisplayValue(q3), "75th percentile of `" + metric + "`"], ["IQR " + columnLabel(metric), chartNumber(iqr), "Q3 - Q1"], ["Rows checked", values.length, "Non-missing numeric rows"]] } }
    ];
  }

  function groupedRateBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(rate|percentage|percent|%)\b/i.test(q)) return [];
    var rateSpec = manualGroupedRateSpec(metadata, q);
    var groupColumn = resolveGroupColumn(metadata, q);
    if (!rateSpec || !rateSpec.column || !groupColumn || groupColumn === rateSpec.column) return [];
    var groups = {};
    var order = [];
    rows.forEach(function (row) {
      var label = String(rowValue(row, groupColumn) || "Blank").trim() || "Blank";
      if (!groups[label]) {
        groups[label] = { total: 0, matching: 0 };
        order.push(label);
      }
      groups[label].total += 1;
      if (rateSpec.positive(row)) groups[label].matching += 1;
    });
    order.sort(function (a, b) {
      var av = groups[a].total ? groups[a].matching / groups[a].total : 0;
      var bv = groups[b].total ? groups[b].matching / groups[b].total : 0;
      return bv - av || a.localeCompare(b);
    });
    var tableRows = [[columnLabel(groupColumn), "Matching Rows", "Rows Checked", rateSpec.label, "Formula"]].concat(order.map(function (label) {
      var item = groups[label];
      return [label, String(item.matching), String(item.total), formatPercent(item.total ? item.matching * 100 / item.total : 0), rateSpec.formula];
    }));
    return [
      { type: "paragraph", data: { text: "Computed " + rateSpec.label + " by `" + groupColumn + "` using each group as its own denominator." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: "bar",
          title: rateSpec.label + " by " + columnLabel(groupColumn),
          labels: order.join(","),
          values: order.map(function (label) { return chartNumber(groups[label].total ? groups[label].matching * 100 / groups[label].total : 0); }).join(","),
          caption: "Per-group denominator: matching rows in group / total rows in group x 100.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      }
    ];
  }

  function singleContributionShareBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(share|contribution|percentage\s+of\s+total|percent\s+of\s+total)\b/i.test(q)) return [];
    var metric = manualMetricColumn(metadata, q);
    var contract = manualIntentContract(metadata, q);
    var groupColumn = shareGroupColumn(metadata, q) || splitGroupByColumns(contract.groupBy)[0] || promptGroupColumns(metadata, q, resolveGroupColumn(metadata, q))[0] || "";
    if (!metric || !groupColumn || metric === groupColumn || !(metadata.numericStats || {})[metric]) return [];
    var groups = {};
    var order = [];
    rows.forEach(function (row) {
      var label = String(rowValue(row, groupColumn) || "Blank").trim() || "Blank";
      if (!groups[label]) {
        groups[label] = { total: 0, rows: 0 };
        order.push(label);
      }
      groups[label].total += rowNumber(row, metric);
      groups[label].rows += 1;
    });
    var grandTotal = order.reduce(function (sum, label) { return sum + groups[label].total; }, 0);
    order.sort(function (a, b) { return groups[b].total - groups[a].total || a.localeCompare(b); });
    var shareLabel = semanticColumnInfo(metadata, metric).role === "measure_sum" && /quantity|qty|units|stock|inventory/i.test(columnKey(metric)) ? "Units Share %" : columnLabel(metric) + " Share %";
    var tableRows = [[columnLabel(groupColumn), shareLabel, columnLabel(metric), "Rows", "Formula"]].concat(order.map(function (label) {
      var share = grandTotal ? groups[label].total * 100 / grandTotal : 0;
      return [label, formatPercent(share), chartNumber(groups[label].total), String(groups[label].rows), "group SUM `" + metric + "` / total SUM `" + metric + "` x 100"];
    }));
    var chartType = /donut|doughnut/i.test(q) ? "doughnut" : "bar";
    return [
      { type: "paragraph", data: { text: "Computed " + columnLabel(metric) + " contribution percentage by `" + groupColumn + "`. " + shareLabel + ". Formula: group sum / grand total sum x 100." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: chartType,
          title: columnLabel(metric) + " Share % by " + columnLabel(groupColumn),
          labels: order.join(","),
          values: order.map(function (label) { return chartNumber(grandTotal ? groups[label].total * 100 / grandTotal : 0); }).join(","),
          caption: "Chart uses share percentages, not encounter/order counts.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2,#db2777,#65a30d"
        }
      }
    ];
  }

  function groupedCountBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    var categoricalCountColumn = categoricalCountValueColumn(metadata, q);
    if (!metadata || !rows.length || (!categoricalCountColumn && !groupedCountQuestion(q) && !/\bencounter\s+count|row\s+count|count\s+rows|count\s+encounters|same\s+analysis/i.test(q))) return [];
    var groupColumns = (categoricalCountColumn ? [categoricalCountColumn] : promptGroupColumns(metadata, q, resolveGroupColumn(metadata, q))).filter(function (column) {
      return column && !isForbiddenColumn(metadata, q, column);
    });
    if (!groupColumns.length) return [];
    var primaryGroup = groupColumns[0];
    var grouped = groupedByColumns(rows, groupColumns);
    var totalRows = rows.length;
    var order = grouped.order.slice();
    var ascending = /\b(lowest\s+to\s+highest|ascending|asc|low\s+to\s+high)\b/i.test(q);
    order.sort(function (a, b) {
      var diff = grouped.grouped[a].count - grouped.grouped[b].count;
      return (ascending ? diff : -diff) || a.localeCompare(b);
    });
    var header = groupColumns.map(function (column) { return columnLabel(column); }).concat(["Count", "Percent"]);
    var tableRows = [header].concat(order.map(function (key) {
      var item = grouped.grouped[key];
      return item.parts.concat([String(item.count), totalRows ? formatPercent(item.count * 100 / totalRows) : "0%"]);
    }));
    var chartLimit = requestedLimit(q, 20);
    var chartOrder = order.slice(0, chartLimit);
    var chartLabels = chartOrder.map(function (key) { return grouped.grouped[key].parts.join(" / "); });
    var chartType = manualChartType(q, "bar");
    return [
      { type: "paragraph", data: { text: "Computed encounter/row counts from " + totalRows + " parsed workbook rows grouped by `" + groupColumns.join("` and `") + "`. Formula: GROUP BY `" + groupColumns.join("`, `") + "`, COUNT rows, sort " + (ascending ? "ascending" : "descending") + " by count." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: chartType,
          title: "Count by " + groupColumns.map(columnLabel).join(" and "),
          labels: manualChartLabelsForType(chartType, chartLabels).join(","),
          values: chartOrder.map(function (key) { return grouped.grouped[key].count; }).join(","),
          xAxisLabel: chartType === "scatter" ? columnLabel(primaryGroup) + " position" : "",
          yAxisLabel: chartType === "scatter" ? "Count" : "",
          caption: "Generated from " + totalRows + " parsed workbook rows. Count uses rows/encounters, not a numeric metric column.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      }
    ];
  }

  function genericMultiMetricGroupBlocks(metadata, question, contract) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || asksForTimeBreakdown(q) || pivotMatrixIntent(metadata, q)) return [];
    if (!executionRequestsGrouping(q) && !/\b(performance|top\s+\d+|bottom\s+\d+|highest|lowest|rank|ranking)\b/i.test(q)) return [];
    if (!/\b(performance|using|show|compare|comparison|group\s+by|by|top\s+\d+|bottom\s+\d+|highest|lowest|rank|ranking)\b/i.test(q)) return [];
    var priorityRankingGroup = rankingValueGroupColumn(metadata, q);
    var contractGroup = contract && contract.groupBy ? splitGroupByColumns(contract.groupBy)[0] : "";
    var groupColumns = contractGroup ? [contractGroup] : (priorityRankingGroup ? [priorityRankingGroup] : promptGroupColumns(metadata, q, resolveGroupColumn(metadata, q)));
    var readmissionMetricColumn = findColumnByWords(metadata, ["readmission", "readmitted", "readmissionrate", "readmission rate", "readmission30days", "readmitted30days", "isreadmitted"]);
    groupColumns = groupColumns.filter(function (column) {
      if (readmissionMetricColumn && column === readmissionMetricColumn && /\breadmission\s+rate|readmission\s*%|readmitted\s+rate\b/i.test(q)) return false;
      return column && !isForbiddenColumn(metadata, q, column) && !metadata.numericStats[column];
    });
    if (!groupColumns.length) return [];
    var metrics = [];
    function addMetric(label, column, mode, formula, positive) {
      if (!column && mode !== "count") return;
      if (metrics.some(function (item) { return item.label === label; })) return;
      metrics.push({ label: label, column: column, mode: mode || "sum", formula: formula || ((mode || "sum").toUpperCase() + " `" + column + "`"), positive: positive || null });
    }
    var readmissionColumn = readmissionMetricColumn;
    if (/\breadmission\s+rate|readmission\s*%|readmitted\s+rate\b/i.test(q)) addMetric("Readmission Rate %", readmissionColumn, "readmissionRate", "readmitted rows / encounter count x 100");
    var groupedRate = manualGroupedRateSpec(metadata, q);
    if (groupedRate) addMetric(groupedRate.label, groupedRate.column, "customRate", groupedRate.formula, groupedRate.positive);
    requestedNumericMeasures(metadata, q).forEach(function (measure) {
      addMetric((measure.mode === "avg" ? "Average " : "Total ") + columnLabel(measure.column), measure.column, measure.mode === "avg" ? "average" : "sum", measureFormula(measure));
    });
    var contractMetric = contract && contract.metric && contract.metric !== "__row_count" ? contract.metric : "";
    var explicitMetric = contractMetric || manualMetricColumn(metadata, q);
    if (explicitMetric && (metadata.numericStats || {})[explicitMetric]) {
      addMetric((aggregationMode(q) === "average" ? "Average " : "Total ") + columnLabel(explicitMetric), explicitMetric, aggregationMode(q) === "average" ? "average" : "sum", (aggregationMode(q) === "average" ? "AVG" : "SUM") + " `" + explicitMetric + "`");
    }
    if (!metrics.length && /\brevenue|sales|total\s+bill|totalbill\b/i.test(q)) addMetric("Revenue", revenueColumn(metadata), "sum", "SUM `" + revenueColumn(metadata) + "`");
    if (/\bprofit\b/i.test(q)) addMetric("Profit", profitColumn(metadata), "sum", "SUM `" + profitColumn(metadata) + "`");
    if (/\bunits?\s+sold|quantity|qty\b/i.test(q)) addMetric("Units Sold", quantityColumn(metadata), "sum", "SUM `" + quantityColumn(metadata) + "`");
    if (/\bdiscount\b/i.test(q)) addMetric("Discount", discountColumn(metadata), "sum", "SUM `" + discountColumn(metadata) + "`");
    if (/\bencounter\s+count|count\s+encounters|encounters?\b/i.test(q)) addMetric("Encounter Count", "", "count", "COUNT rows/encounters");
    else if (/\border\s+count|count\s+orders|orders?\b/i.test(q)) addMetric("Order Count", "", "count", "COUNT rows/orders");
    else if (/\band\s+count\b|\bwith\s+count\b|\binclude\s+count\b|\+\s*count\b|\bcount\s+(?:also|too)\b/i.test(q)) addMetric("Count", "", "count", "COUNT rows");
    if (/\brevenue\s+per\s+unit|average\s+revenue\s+per\s+unit|avg\s+revenue\s+per\s+unit\b/i.test(q)) addMetric("Average Revenue per Unit", "", "ratioRevenueQuantity", "SUM Revenue / SUM Units Sold");
    metrics = metrics.filter(function (metric, index, arr) {
      return arr.findIndex(function (other) { return other.label === metric.label; }) === index;
    });
    if (!metrics.length) return [];
    if (metrics.length < 2 && !/\bperformance\b/i.test(q) && !metricByGroupQuestion(q)) return [];
    var grouped = groupedByColumns(rows, groupColumns);
    var items = grouped.order.map(function (key) {
      var entry = grouped.grouped[key];
      var item = { labels: entry.parts, rows: entry.count, values: {} };
      var revenue = revenueColumn(metadata);
      var quantity = quantityColumn(metadata);
      metrics.forEach(function (metric) {
        if (metric.mode === "count") item.values[metric.label] = entry.count;
        else if (metric.mode === "readmissionRate") {
          var readmitted = metric.column ? entry.rows.filter(function (row) { return truthySheetValue(rowValue(row, metric.column)); }).length : 0;
          item.values[metric.label] = entry.count ? readmitted * 100 / entry.count : 0;
        }
        else if (metric.mode === "customRate") {
          var matches = metric.positive ? entry.rows.filter(metric.positive).length : 0;
          item.values[metric.label] = entry.count ? matches * 100 / entry.count : 0;
        }
        else if (metric.mode === "ratioRevenueQuantity") {
          var rev = revenue ? aggregateRows(entry.rows, revenue, "sum") : 0;
          var qty = quantity ? aggregateRows(entry.rows, quantity, "sum") : 0;
          item.values[metric.label] = qty ? rev / qty : 0;
        } else item.values[metric.label] = aggregateRows(entry.rows, metric.column, metric.mode);
      });
      return item;
    });
    var primary = metrics[0].label;
    var bottom = rankingIsBottom(q);
    items.sort(function (a, b) {
      var av = Number(a.values[primary] || 0);
      var bv = Number(b.values[primary] || 0);
      return (bottom ? av - bv : bv - av) || a.labels.join(" / ").localeCompare(b.labels.join(" / "));
    });
    items = items.slice(0, requestedLimit(q, 20));
    var headers = groupColumns.concat(metrics.map(function (metric) { return metric.label; }));
    var tableRows = [headers].concat(items.map(function (item) {
      return item.labels.concat(metrics.map(function (metric) {
        var suffix = /%$/.test(metric.label) ? "%" : "";
        return String(chartNumber(item.values[metric.label] || 0)) + suffix;
      }));
    }));
    var chartMetrics = metrics.slice(0, Math.min(metrics.length, 3));
    return [
      { type: "paragraph", data: { text: "Computed grouped performance from " + rows.length + " parsed workbook rows grouped by `" + groupColumns.join("` + `") + "`. Formulas: " + metrics.map(function (metric) { return metric.label + " = " + metric.formula; }).join("; ") + "." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: chartMetrics.length > 1 ? "comparison" : "bar",
          title: "Grouped Performance by " + groupColumns.join(" + "),
          labels: items.map(function (item) { return item.labels.join(" / "); }).join(","),
          values: chartMetrics.map(function (metric) {
            return items.map(function (item) { return chartNumber(item.values[metric.label] || 0); }).join(",");
          }).join(";"),
          seriesLabels: chartMetrics.map(function (metric) { return metric.label; }).join(","),
          caption: "Chart uses the leading requested metrics; table contains every requested metric.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b"
        }
      }
    ];
  }

  function combinationRankingBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(city-category|city\s+category|city\s+and\s+category|combinations?|combo)\b/i.test(q)) return [];
    var city = cityColumn(metadata);
    var category = categoryColumn(metadata);
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    if (!city || !category || !revenue || !profit) return [];
    var minCountMatch = q.match(/\b(?:count|orders?)\s+is\s+at\s+least\s+(\d+)|\bat\s+least\s+(\d+)\b/i);
    var minCount = minCountMatch ? Number(minCountMatch[1] || minCountMatch[2]) || 0 : 0;
    var bottom = /\bbottom|lowest|least\b/i.test(q);
    var limit = requestedLimit(q, 5);
    var grouped = {};
    rows.forEach(function (row) {
      var parts = groupKeyParts(row, [city, category]);
      var key = parts.join("||");
      if (!grouped[key]) grouped[key] = { city: parts[0], category: parts[1], revenue: 0, profit: 0, count: 0 };
      grouped[key].revenue += rowNumber(row, revenue);
      grouped[key].profit += rowNumber(row, profit);
      grouped[key].count += 1;
    });
    var items = Object.keys(grouped).map(function (key) {
      var item = grouped[key];
      item.margin = item.revenue ? item.profit / item.revenue * 100 : 0;
      return item;
    }).filter(function (item) { return item.count >= minCount; });
    var sortMetric = /\bprofit\s+margin|margin\b/i.test(q) ? "margin" : "profit";
    items.sort(function (a, b) { return (bottom ? a[sortMetric] - b[sortMetric] : b[sortMetric] - a[sortMetric]) || b.profit - a.profit; });
    items = items.slice(0, limit);
    return [
      { type: "paragraph", data: { text: "Computed " + (bottom ? "bottom" : "top") + " " + limit + " `" + city + "` + `" + category + "` combinations from " + rows.length + " parsed workbook rows" + (minCount ? " with order count at least " + minCount : "") + ". Profit Margin % = Profit / Revenue x 100." } },
      { type: "table", data: { withHeadings: true, rows: [["City", "Category", "Revenue", "Profit", "Order Count", "Profit Margin %"]].concat(items.map(function (item) {
        return [item.city, item.category, String(chartNumber(item.revenue)), String(chartNumber(item.profit)), String(item.count), chartNumber(item.margin) + "%"];
      })) } },
      {
        type: "chart",
        data: {
          type: "bar",
          title: (bottom ? "Bottom" : "Top") + " " + limit + " City-Category by " + (sortMetric === "margin" ? "Profit Margin %" : "Profit"),
          labels: items.map(function (item) { return item.city + " / " + item.category; }).join(","),
          values: items.map(function (item) { return chartNumber(item[sortMetric]); }).join(","),
          caption: "Generated from " + rows.length + " parsed workbook rows. Formula: group by city and category, sum Revenue/Profit, count orders, then compute Profit Margin %.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed"
        }
      }
    ];
  }

  function monthOverMonthConditionBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(increased|decreased|increase|decrease|compared\s+with\s+the\s+previous|previous\s+month|month\s+over\s+month|mom)\b/i.test(q) || !/\brevenue\b/i.test(q) || !/\bprofit\b/i.test(q)) return [];
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "month"]);
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    if (!dateColumn || !revenue || !profit) return [];
    var months = {};
    var order = [];
    rows.forEach(function (row) {
      var bucket = monthBucket(rowValue(row, dateColumn));
      if (!bucket) return;
      if (!months[bucket.label]) {
        months[bucket.label] = { key: bucket.key, revenue: 0, profit: 0, count: 0 };
        order.push(bucket.label);
      }
      months[bucket.label].revenue += rowNumber(row, revenue);
      months[bucket.label].profit += rowNumber(row, profit);
      months[bucket.label].count += 1;
    });
    order.sort(function (a, b) { return months[a].key - months[b].key; });
    var matches = [];
    for (var i = 1; i < order.length; i += 1) {
      var current = months[order[i]];
      var previous = months[order[i - 1]];
      var revenueChange = current.revenue - previous.revenue;
      var profitChange = current.profit - previous.profit;
      if (revenueChange > 0 && profitChange < 0) {
        matches.push({
          month: order[i],
          previousMonth: order[i - 1],
          revenue: current.revenue,
          profit: current.profit,
          revenueChange: revenueChange,
          profitChange: profitChange,
          reason: "Revenue rose while profit fell, which usually indicates lower margin mix, higher discounts/costs, or less profitable products/orders in the month."
        });
      }
    }
    var tableRows = [["Month", "Previous Month", "Revenue Change", "Profit Change", "Revenue", "Profit", "Possible Reason"]].concat(matches.map(function (item) {
      return [item.month, item.previousMonth, String(chartNumber(item.revenueChange)), String(chartNumber(item.profitChange)), String(chartNumber(item.revenue)), String(chartNumber(item.profit)), item.reason];
    }));
    if (!matches.length) {
      tableRows.push(["No matching month", "", "0", "0", "", "", "No month had Revenue increase while Profit decreased compared with the previous month."]);
    }
    return [
      { type: "paragraph", data: { text: "Checked month-over-month changes from " + rows.length + " parsed workbook rows. Formula: monthly Revenue change = current month Revenue - previous month Revenue; monthly Profit change = current month Profit - previous month Profit. Returned months where Revenue change > 0 and Profit change < 0." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: "Revenue Increase vs Profit Decrease Months",
          labels: matches.length ? matches.map(function (item) { return item.month; }).join(",") : "No matching month",
          values: matches.length ? matches.map(function (item) { return chartNumber(item.revenueChange); }).join(",") + ";" + matches.map(function (item) { return chartNumber(item.profitChange); }).join(",") : "0;0",
          seriesLabels: "Revenue Change,Profit Change",
          caption: "Generated from full parsed rows using month-over-month deltas. Positive Revenue Change and negative Profit Change identify the requested months.",
          color: "#2563eb",
          colors: "#2563eb,#dc2626"
        }
      }
    ];
  }

  function outlierRowBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(outlier|unusually|top\s+\d+|highest|largest)\b/i.test(q) || !/\bdiscount\b/i.test(q)) return [];
    var discount = discountColumn(metadata);
    var profit = profitColumn(metadata);
    var revenue = revenueColumn(metadata);
    var category = categoryColumn(metadata);
    var product = resolveNamedColumn(metadata, ["product", "productname", "item", "sku"], "category");
    var city = cityColumn(metadata);
    if (!discount) return [];
    var limit = requestedLimit(q, 20);
    var sorted = rows.slice().sort(function (a, b) { return rowNumber(b, discount) - rowNumber(a, discount); }).slice(0, limit);
    var headers = ["Rank", discount];
    if (profit) headers.push(profit);
    if (revenue) headers.push(revenue);
    if (category) headers.push(category);
    if (product) headers.push(product);
    if (city) headers.push(city);
    var tableRows = [headers].concat(sorted.map(function (row, index) {
      var out = [String(index + 1), String(chartNumber(rowNumber(row, discount)))];
      if (profit) out.push(String(chartNumber(rowNumber(row, profit))));
      if (revenue) out.push(String(chartNumber(rowNumber(row, revenue))));
      if (category) out.push(String(rowValue(row, category) || ""));
      if (product) out.push(String(rowValue(row, product) || ""));
      if (city) out.push(String(rowValue(row, city) || ""));
      return out;
    }));
    return [
      { type: "paragraph", data: { text: "Identified top " + limit + " outlier rows by `" + discount + "` from " + rows.length + " parsed workbook rows. This is intentionally row-level because the prompt asked for outlier orders." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: "bar",
          title: "Top " + limit + " Orders by " + discount,
          labels: sorted.map(function (_, index) { return "Order " + (index + 1); }).join(","),
          values: sorted.map(function (row) { return chartNumber(rowNumber(row, discount)); }).join(","),
          caption: "Generated from row-level data. Formula: sort all rows by `" + discount + "` descending and show the top " + limit + ".",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed"
        }
      }
    ];
  }

  function timeTrendMetricBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !asksForTimeBreakdown(q) || asksForDistribution(q)) return [];
    var explicitDate = q.match(/\busing\s+([A-Za-z][A-Za-z0-9_ ]{1,60})(?:[.,;]|$)/i);
    var dateColumn = explicitDate ? resolvePromptColumnPhrase(metadata, explicitDate[1], "date") : "";
    if (dateColumn && (metadata.dateColumns || []).indexOf(dateColumn) < 0 && !/date|month|year|time/i.test(String(dateColumn || ""))) dateColumn = "";
    if (!dateColumn || (metadata.numericStats || {})[dateColumn]) {
      dateColumn = (metadata.dateColumns || []).filter(function (column) {
        return !semanticColumnInfo(metadata, column).identifier && /date|month|time|year/i.test(String(column || ""));
      })[0] || findColumnByWords(metadata, ["date", "month", "created_at", "updated_at"]);
    }
    if (!dateColumn) return [];
    var metrics = mentionedNumericColumns(metadata, q).filter(function (metric) {
      return metric !== dateColumn && !/^(year|month|quarter|week)$/i.test(String(metric || "")) && !semanticColumnInfo(metadata, metric).identifier;
    });
    if (!metrics.length) metrics = [manualMetricColumn(metadata, q)].filter(Boolean);
    metrics = metrics.filter(function (metric, index, list) {
      return metric && metric !== dateColumn && list.indexOf(metric) === index;
    });
    metrics = metrics.slice(0, 4);
    if (!metrics.length) return [];
    var mode = aggregationMode(q);
    var qKey = columnKey(q);
    var useYear = /\b(year|yearly|annual|year-wise|year\s+wise)\b/i.test(q) || qKey.indexOf("yearwise") >= 0 || qKey.indexOf("byyear") >= 0;
    var bucketLabel = useYear ? "Year" : "Month";
    var bucketFn = useYear ? yearBucket : monthBucket;
    var buckets = {};
    var order = [];
    rows.forEach(function (row) {
      var bucket = bucketFn(rowValue(row, dateColumn));
      if (!bucket) return;
      if (!buckets[bucket.label]) {
        buckets[bucket.label] = { key: bucket.key, rows: [] };
        order.push(bucket.label);
      }
      buckets[bucket.label].rows.push(row);
    });
    order.sort(function (a, b) { return buckets[a].key - buckets[b].key; });
    if (!order.length) return [];
    var tableRows = [[bucketLabel].concat(metrics).concat(["Rows"])];
    order.forEach(function (period) {
      tableRows.push([period].concat(metrics.map(function (metric) {
        return String(chartNumber(aggregateRows(buckets[period].rows, metric, mode)));
      })).concat([String(buckets[period].rows.length)]));
    });
    var values = metrics.map(function (metric) {
      return order.map(function (period) { return chartNumber(aggregateRows(buckets[period].rows, metric, mode)); }).join(",");
    }).join(";");
    var requested = requestedChartType(q);
    var chartType = metrics.length > 1 ? (requested === "line" || requested === "stackedBar" || requested === "comparison" ? requested : "comparison") : manualChartType(q, asksForTrend(q) ? "line" : "bar");
    var summary = "Computed " + mode + " " + (useYear ? "yearly" : "monthly") + " trend from " + rows.length + " workbook rows using `" + dateColumn + "` and metric" + (metrics.length > 1 ? "s" : "") + " `" + metrics.join("`, `") + "`.";
    return [
      { type: "paragraph", data: { text: summary + " Formula: group rows by " + bucketLabel.toLowerCase() + ", then " + mode + " each metric inside the " + bucketLabel.toLowerCase() + "." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: chartType,
          title: metrics.join(" vs ") + " by " + bucketLabel,
          labels: order.join(","),
          values: values,
          seriesLabels: metrics.join(","),
          caption: "Generated from " + rows.length + " parsed workbook rows. Formula: " + mode + " by " + bucketLabel.toLowerCase() + " from `" + dateColumn + "`. Series colors map to: " + metrics.join(", ") + ".",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      }
    ];
  }

  function customerSegmentMonthlyFallbackBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\bcustomer\s+segments?\b/i.test(q) || !asksForTimeBreakdown(q)) return [];
    var segment = customerSegmentColumn(metadata);
    if (segment) return [];
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "month"]);
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    if (!dateColumn || !revenue) return [];
    var orderId = orderIdentifierColumn(metadata);
    var months = {};
    var order = [];
    rows.forEach(function (row) {
      var bucket = monthBucket(rowValue(row, dateColumn));
      if (!bucket) return;
      if (!months[bucket.label]) {
        months[bucket.label] = { key: bucket.key, revenue: 0, profit: 0, rows: 0, ids: {} };
        order.push(bucket.label);
      }
      months[bucket.label].revenue += rowNumber(row, revenue);
      months[bucket.label].profit += profit ? rowNumber(row, profit) : 0;
      months[bucket.label].rows += 1;
      if (orderId) months[bucket.label].ids[String(rowValue(row, orderId) || months[bucket.label].rows)] = true;
    });
    order.sort(function (a, b) { return months[a].key - months[b].key; });
    var tableRows = [["Month", "Revenue", "Profit", "Order Count"]].concat(order.map(function (month) {
      return [month, String(chartNumber(months[month].revenue)), String(chartNumber(months[month].profit)), String(orderId ? Object.keys(months[month].ids).length : months[month].rows)];
    }));
    return [
      { type: "paragraph", data: { text: "No `Customer Segment` column was found, and the prompt says not to group by customer name. I therefore used monthly totals only from all " + rows.length + " parsed workbook rows. Formula: GROUP BY Month, SUM Revenue, SUM Profit, and " + (orderId ? "COUNT_DISTINCT `" + orderId + "`" : "COUNT rows") + "." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: "Monthly Revenue and Profit",
          labels: order.join(","),
          values: order.map(function (month) { return chartNumber(months[month].revenue); }).join(",") + ";" + order.map(function (month) { return chartNumber(months[month].profit); }).join(","),
          seriesLabels: "Revenue,Profit",
          caption: "Customer segment was unavailable, so this chart uses month-level totals only and does not group by customer name. Order Count is kept in the table because it uses a much smaller scale than Revenue and Profit.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a"
        }
      },
      {
        type: "chart",
        data: {
          type: "bar",
          title: "Monthly Order Count",
          labels: order.join(","),
          values: order.map(function (month) { return chartNumber(orderId ? Object.keys(months[month].ids).length : months[month].rows); }).join(","),
          caption: "Separate count chart prevents order volume from disappearing beside large revenue/profit values.",
          color: "#f59e0b",
          colors: "#f59e0b"
        }
      }
    ];
  }

  function cityProfitMarginBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\bcit(?:y|ies)\b/i.test(q) || !/\bprofit\s+margin\b/i.test(q)) return [];
    var city = cityColumn(metadata);
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    if (!city || !revenue || !profit) return [];
    var minMatch = q.match(/\bfewer\s+than\s+(\d+)\s+orders|\bat\s+least\s+(\d+)\s+orders/i);
    var minOrders = minMatch ? Number(minMatch[1] || minMatch[2]) || 0 : 0;
    var groups = {};
    Object.keys(rows).forEach(function (key) {
      var row = rows[key];
      var label = String(rowValue(row, city) || "Blank").trim() || "Blank";
      if (!groups[label]) groups[label] = { revenue: 0, profit: 0, rows: 0 };
      groups[label].revenue += rowNumber(row, revenue);
      groups[label].profit += rowNumber(row, profit);
      groups[label].rows += 1;
    });
    var items = Object.keys(groups).map(function (label) {
      var item = groups[label];
      return { city: label, revenue: item.revenue, profit: item.profit, rows: item.rows, margin: item.revenue ? item.profit / item.revenue * 100 : 0 };
    }).filter(function (item) { return item.rows >= minOrders; });
    items.sort(function (a, b) { return b.margin - a.margin || b.profit - a.profit; });
    var limit = requestedLimit(q, items.length || 10);
    items = items.slice(0, limit);
    return [
      { type: "paragraph", data: { text: "Computed top cities by Profit Margin % from " + rows.length + " parsed workbook rows" + (minOrders ? ", ignoring cities with fewer than " + minOrders + " orders" : "") + ". Formula: Profit Margin % = SUM Profit / SUM Revenue x 100; sorted by margin, not total profit." } },
      { type: "table", data: { withHeadings: true, rows: [["City", "Revenue", "Profit", "Order Count", "Profit Margin %"]].concat(items.map(function (item) {
        return [item.city, String(chartNumber(item.revenue)), String(chartNumber(item.profit)), String(item.rows), chartNumber(item.margin) + "%"];
      })) } },
      {
        type: "chart",
        data: {
          type: manualChartType(q, "bar"),
          title: "Top Cities by Profit Margin %",
          labels: items.map(function (item) { return item.city; }).join(","),
          values: items.map(function (item) { return chartNumber(item.margin); }).join(","),
          caption: "Generated from all parsed rows. Formula: city Profit Margin % = city Profit / city Revenue x 100.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      }
    ];
  }

  function paymentShareComparisonBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\bpayment\s*method\b/i.test(q) || !/\bshare\b/i.test(q) || !/\brevenue\b/i.test(q) || !/\border/i.test(q)) return [];
    var payment = paymentColumn(metadata);
    var revenue = revenueColumn(metadata);
    if (!payment || !revenue) return [];
    var groups = {};
    var order = [];
    rows.forEach(function (row) {
      var label = String(rowValue(row, payment) || "Blank").trim() || "Blank";
      if (!groups[label]) {
        groups[label] = { revenue: 0, rows: 0 };
        order.push(label);
      }
      groups[label].revenue += rowNumber(row, revenue);
      groups[label].rows += 1;
    });
    var totalRevenue = order.reduce(function (sum, label) { return sum + groups[label].revenue; }, 0);
    var totalRows = order.reduce(function (sum, label) { return sum + groups[label].rows; }, 0);
    order.sort(function (a, b) { return (groups[b].revenue / Math.max(1, totalRevenue)) - (groups[a].revenue / Math.max(1, totalRevenue)) || a.localeCompare(b); });
    var revenueShare = order.map(function (label) { return chartNumber(totalRevenue ? groups[label].revenue * 100 / totalRevenue : 0); });
    var orderShare = order.map(function (label) { return chartNumber(totalRows ? groups[label].rows * 100 / totalRows : 0); });
    return [
      { type: "paragraph", data: { text: "Compared `" + payment + "` share of Revenue vs share of Orders from " + rows.length + " parsed workbook rows. Formula: Revenue Share % = payment Revenue / total Revenue x 100; Order Share % = payment Order Count / total Order Count x 100." } },
      { type: "table", data: { withHeadings: true, rows: [["PaymentMethod", "Revenue", "Revenue Share %", "Order Count", "Order Share %"]].concat(order.map(function (label, index) {
        return [label, String(chartNumber(groups[label].revenue)), revenueShare[index] + "%", String(groups[label].rows), orderShare[index] + "%"];
      })) } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: "Revenue Share % vs Order Share % by " + payment,
          labels: order.join(","),
          values: revenueShare.join(",") + ";" + orderShare.join(","),
          seriesLabels: "Revenue Share %,Order Share %",
          caption: "Side-by-side percentages use the same denominator logic requested in the prompt.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a"
        }
      }
    ];
  }

  function categoryContributionComparisonBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\bcategor(?:y|ies)\b/i.test(q) || !/\bprofit\s+contribution\b/i.test(q) || !/\brevenue\s+contribution\b/i.test(q)) return [];
    var category = categoryColumn(metadata);
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    if (!category || !revenue || !profit) return [];
    var groups = {};
    var order = [];
    rows.forEach(function (row) {
      var label = String(rowValue(row, category) || "Blank").trim() || "Blank";
      if (!groups[label]) {
        groups[label] = { revenue: 0, profit: 0, rows: 0 };
        order.push(label);
      }
      groups[label].revenue += rowNumber(row, revenue);
      groups[label].profit += rowNumber(row, profit);
      groups[label].rows += 1;
    });
    var totalRevenue = order.reduce(function (sum, label) { return sum + groups[label].revenue; }, 0);
    var totalProfit = order.reduce(function (sum, label) { return sum + groups[label].profit; }, 0);
    var items = order.map(function (label) {
      var revenueShare = totalRevenue ? groups[label].revenue * 100 / totalRevenue : 0;
      var profitShare = totalProfit ? groups[label].profit * 100 / totalProfit : 0;
      return { label: label, revenue: groups[label].revenue, profit: groups[label].profit, rows: groups[label].rows, revenueShare: revenueShare, profitShare: profitShare, gap: profitShare - revenueShare };
    }).filter(function (item) { return item.profitShare < item.revenueShare; });
    if (!items.length) items = order.map(function (label) {
      var revenueShare = totalRevenue ? groups[label].revenue * 100 / totalRevenue : 0;
      var profitShare = totalProfit ? groups[label].profit * 100 / totalProfit : 0;
      return { label: label, revenue: groups[label].revenue, profit: groups[label].profit, rows: groups[label].rows, revenueShare: revenueShare, profitShare: profitShare, gap: profitShare - revenueShare };
    });
    items.sort(function (a, b) { return a.gap - b.gap || a.label.localeCompare(b.label); });
    return [
      { type: "paragraph", data: { text: "Found categories where Profit contribution is lower than Revenue contribution from " + rows.length + " parsed workbook rows. Formula: Revenue Contribution % = category Revenue / total Revenue x 100; Profit Contribution % = category Profit / total Profit x 100; Gap = Profit Contribution % - Revenue Contribution %." } },
      { type: "table", data: { withHeadings: true, rows: [["Category", "Revenue", "Revenue Contribution %", "Profit", "Profit Contribution %", "Gap"]].concat(items.map(function (item) {
        return [item.label, String(chartNumber(item.revenue)), chartNumber(item.revenueShare) + "%", String(chartNumber(item.profit)), chartNumber(item.profitShare) + "%", chartNumber(item.gap) + "%"];
      })) } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: "Revenue Contribution % vs Profit Contribution %",
          labels: items.map(function (item) { return item.label; }).join(","),
          values: items.map(function (item) { return chartNumber(item.revenueShare); }).join(",") + ";" + items.map(function (item) { return chartNumber(item.profitShare); }).join(","),
          seriesLabels: "Revenue Contribution %,Profit Contribution %",
          caption: "Lower profit contribution than revenue contribution indicates weaker relative margin contribution.",
          color: "#2563eb",
          colors: "#2563eb,#dc2626"
        }
      }
    ];
  }

  function monthlyFormulaComparisonBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !asksForTimeBreakdown(q) || !/\bprofit\s+margin\b/i.test(q) || !/\bdiscount\s*%|discount\s+percentage\b/i.test(q)) return [];
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "month"]);
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    var discount = discountColumn(metadata);
    if (!dateColumn || !revenue || !profit || !discount) return [];
    var months = {};
    var order = [];
    rows.forEach(function (row) {
      var bucket = monthBucket(rowValue(row, dateColumn));
      if (!bucket) return;
      if (!months[bucket.label]) {
        months[bucket.label] = { key: bucket.key, revenue: 0, profit: 0, discount: 0, rows: 0 };
        order.push(bucket.label);
      }
      months[bucket.label].revenue += rowNumber(row, revenue);
      months[bucket.label].profit += rowNumber(row, profit);
      months[bucket.label].discount += rowNumber(row, discount);
      months[bucket.label].rows += 1;
    });
    order.sort(function (a, b) { return months[a].key - months[b].key; });
    var discountPct = order.map(function (month) { return chartNumber(months[month].revenue ? months[month].discount * 100 / months[month].revenue : 0); });
    var marginPct = order.map(function (month) { return chartNumber(months[month].revenue ? months[month].profit * 100 / months[month].revenue : 0); });
    return [
      { type: "paragraph", data: { text: "Computed monthly Profit Margin % and Discount % from " + rows.length + " parsed workbook rows. Formula: Discount % = monthly Discount / monthly Revenue x 100; Profit Margin % = monthly Profit / monthly Revenue x 100." } },
      { type: "table", data: { withHeadings: true, rows: [["Month", "Revenue", "Discount", "Discount %", "Profit", "Profit Margin %", "Rows"]].concat(order.map(function (month, index) {
        return [month, String(chartNumber(months[month].revenue)), String(chartNumber(months[month].discount)), discountPct[index] + "%", String(chartNumber(months[month].profit)), marginPct[index] + "%", String(months[month].rows)];
      })) } },
      {
        type: "chart",
        data: {
          type: manualChartType(q, "line"),
          title: "Monthly Discount % and Profit Margin %",
          labels: order.join(","),
          values: discountPct.join(",") + ";" + marginPct.join(","),
          seriesLabels: "Discount %,Profit Margin %",
          caption: "Two separate percentage series are shown with clear labels; both are calculated from month-level totals.",
          color: "#2563eb",
          colors: "#f59e0b,#16a34a"
        }
      }
    ];
  }

  function aovByMonthCategoryBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(aov|average\s+order\s+value)\b/i.test(q) || !asksForTimeBreakdown(q) || !/\bcategor(?:y|ies)\b/i.test(q)) return [];
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "month"]);
    var category = categoryColumn(metadata);
    var revenue = revenueColumn(metadata);
    var orderId = orderIdentifierColumn(metadata);
    if (!dateColumn || !category || !revenue) return [];
    var matrix = {};
    var monthOrder = [];
    var categories = {};
    var categoryOrder = [];
    rows.forEach(function (row) {
      var bucket = monthBucket(rowValue(row, dateColumn));
      if (!bucket) return;
      var cat = String(rowValue(row, category) || "Blank").trim() || "Blank";
      if (!matrix[bucket.label]) {
        matrix[bucket.label] = { key: bucket.key, categories: {} };
        monthOrder.push(bucket.label);
      }
      if (!categories[cat]) {
        categories[cat] = true;
        categoryOrder.push(cat);
      }
      if (!matrix[bucket.label].categories[cat]) matrix[bucket.label].categories[cat] = { revenue: 0, rows: 0, ids: {} };
      matrix[bucket.label].categories[cat].revenue += rowNumber(row, revenue);
      matrix[bucket.label].categories[cat].rows += 1;
      if (orderId) matrix[bucket.label].categories[cat].ids[String(rowValue(row, orderId) || matrix[bucket.label].categories[cat].rows)] = true;
    });
    monthOrder.sort(function (a, b) { return matrix[a].key - matrix[b].key; });
    categoryOrder.sort();
    var tableRows = [["Month", "Category", "Revenue", "Order Count", "AOV"]];
    monthOrder.forEach(function (month) {
      categoryOrder.forEach(function (cat) {
        var item = matrix[month].categories[cat] || { revenue: 0, rows: 0, ids: {} };
        var count = orderId ? Object.keys(item.ids).length : item.rows;
        tableRows.push([month, cat, String(chartNumber(item.revenue)), String(count), String(chartNumber(count ? item.revenue / count : 0))]);
      });
    });
    var values = categoryOrder.map(function (cat) {
      return monthOrder.map(function (month) {
        var item = matrix[month].categories[cat] || { revenue: 0, rows: 0, ids: {} };
        var count = orderId ? Object.keys(item.ids).length : item.rows;
        return chartNumber(count ? item.revenue / count : 0);
      }).join(",");
    }).join(";");
    return [
      { type: "paragraph", data: { text: "Computed AOV by Month and `" + category + "` from " + rows.length + " parsed workbook rows. Formula: AOV = SUM Revenue / " + (orderId ? "COUNT_DISTINCT `" + orderId + "`" : "COUNT rows") + " for each Month + Category group." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: manualChartType(q, "line"),
          title: "AOV by Month and " + category,
          labels: monthOrder.join(","),
          values: values,
          seriesLabels: categoryOrder.join(","),
          caption: "Each line is one `" + category + "` series. AOV is computed from grouped revenue divided by grouped order count.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      }
    ];
  }

  function waterfallRevenueNetProfitBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\bwaterfall|net\s+revenue\b/i.test(q) || !/\brevenue\b/i.test(q) || !/\bprofit\b/i.test(q)) return [];
    var category = categoryColumn(metadata) || resolveGroupColumn(metadata, q);
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    var discount = discountColumn(metadata);
    if (!category || !revenue || !profit || !discount) return [];
    var groups = {};
    var order = [];
    rows.forEach(function (row) {
      var label = String(rowValue(row, category) || "Blank").trim() || "Blank";
      if (!groups[label]) {
        groups[label] = { revenue: 0, discount: 0, profit: 0, rows: 0 };
        order.push(label);
      }
      groups[label].revenue += rowNumber(row, revenue);
      groups[label].discount += rowNumber(row, discount);
      groups[label].profit += rowNumber(row, profit);
      groups[label].rows += 1;
    });
    order.sort(function (a, b) { return groups[b].revenue - groups[a].revenue || a.localeCompare(b); });
    return [
      { type: "paragraph", data: { text: "Created a waterfall-style Revenue to Net Revenue to Profit explanation by `" + category + "` from " + rows.length + " parsed workbook rows. Formula: Net Revenue after Discount = Revenue - Discount; Profit Margin % = Profit / Revenue x 100." } },
      { type: "table", data: { withHeadings: true, rows: [["Category", "Revenue", "Discount", "Net Revenue after Discount", "Profit", "Profit Margin %", "Rows"]].concat(order.map(function (label) {
        var item = groups[label];
        return [label, String(chartNumber(item.revenue)), String(chartNumber(item.discount)), String(chartNumber(item.revenue - item.discount)), String(chartNumber(item.profit)), chartNumber(item.revenue ? item.profit * 100 / item.revenue : 0) + "%", String(item.rows)];
      })) } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: "Revenue to Net Revenue to Profit by " + category,
          labels: order.join(","),
          values: order.map(function (label) { return chartNumber(groups[label].revenue); }).join(",") + ";" + order.map(function (label) { return chartNumber(groups[label].revenue - groups[label].discount); }).join(",") + ";" + order.map(function (label) { return chartNumber(groups[label].profit); }).join(","),
          seriesLabels: "Revenue,Net Revenue after Discount,Profit",
          caption: "Waterfall-style comparison: Revenue minus Discount gives Net Revenue after Discount, then Profit is shown as the final business result.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#dc2626"
        }
      }
    ];
  }

  function riskyProductsBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\brisky\s+products?|high\s+discount\s*%|low\s+profit\s+margin\b/i.test(q) || !/\bproducts?\b/i.test(q)) return [];
    var product = resolveNamedColumn(metadata, ["product", "productname", "item", "sku"], "category");
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    var discount = discountColumn(metadata);
    if (!product || !revenue || !profit || !discount) return [];
    var groups = {};
    var labels = [];
    rows.forEach(function (row) {
      var label = String(rowValue(row, product) || "Blank").trim() || "Blank";
      if (!groups[label]) {
        groups[label] = { revenue: 0, profit: 0, discount: 0, rows: 0 };
        labels.push(label);
      }
      groups[label].revenue += rowNumber(row, revenue);
      groups[label].profit += rowNumber(row, profit);
      groups[label].discount += rowNumber(row, discount);
      groups[label].rows += 1;
    });
    var avgRevenue = labels.reduce(function (sum, label) { return sum + groups[label].revenue; }, 0) / Math.max(1, labels.length);
    var items = labels.map(function (label) {
      var item = groups[label];
      var discountPct = item.revenue ? item.discount * 100 / item.revenue : 0;
      var marginPct = item.revenue ? item.profit * 100 / item.revenue : 0;
      return { product: label, revenue: item.revenue, profit: item.profit, discount: item.discount, rows: item.rows, discountPct: discountPct, marginPct: marginPct };
    }).filter(function (item) { return item.revenue > avgRevenue; });
    var highestMargin = items.reduce(function (maxValue, item) { return Math.max(maxValue, item.marginPct); }, 0);
    items.forEach(function (item) {
      item.riskPriority = item.discountPct + Math.max(0, highestMargin - item.marginPct);
    });
    items.sort(function (a, b) { return b.riskPriority - a.riskPriority || b.discountPct - a.discountPct || a.marginPct - b.marginPct; });
    items = items.slice(0, requestedLimit(q, 10));
    return [
      { type: "paragraph", data: { text: "Identified top " + items.length + " risky products from " + rows.length + " parsed workbook rows where product Revenue is above average. Formula: Discount % = SUM Discount / SUM Revenue x 100; Profit Margin % = SUM Profit / SUM Revenue x 100; Risk Priority = Discount % + margin weakness, where margin weakness = highest eligible Profit Margin % - product Profit Margin %." } },
      { type: "table", data: { withHeadings: true, rows: [["Product", "Revenue", "Discount", "Discount %", "Profit", "Profit Margin %", "Order Count", "Risk Priority"]].concat(items.map(function (item) {
        return [item.product, String(chartNumber(item.revenue)), String(chartNumber(item.discount)), chartNumber(item.discountPct) + "%", String(chartNumber(item.profit)), chartNumber(item.marginPct) + "%", String(item.rows), chartNumber(item.riskPriority)];
      })) } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: "Risky Products: Discount % vs Profit Margin %",
          labels: items.map(function (item) { return item.product; }).join(","),
          values: items.map(function (item) { return chartNumber(item.discountPct); }).join(",") + ";" + items.map(function (item) { return chartNumber(item.marginPct); }).join(","),
          seriesLabels: "Discount %,Profit Margin %",
          caption: "Products are filtered to revenue above average and sorted by high discount pressure with lower margin.",
          color: "#2563eb",
          colors: "#f59e0b,#16a34a"
        }
      }
    ];
  }

  function codVsNonCodMonthlyBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\bcod\b/i.test(q) || !/\bnon[-\s]?cod\b/i.test(q) || !asksForTimeBreakdown(q)) return [];
    var payment = paymentColumn(metadata);
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "month"]);
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    if (!payment || !dateColumn || !revenue || !profit) return [];
    var months = {};
    var order = [];
    rows.forEach(function (row) {
      var bucket = monthBucket(rowValue(row, dateColumn));
      if (!bucket) return;
      var kind = /^cod$/i.test(String(rowValue(row, payment) || "").trim()) ? "COD" : "Non-COD";
      if (!months[bucket.label]) {
        months[bucket.label] = { key: bucket.key, "COD": { revenue: 0, profit: 0, count: 0 }, "Non-COD": { revenue: 0, profit: 0, count: 0 } };
        order.push(bucket.label);
      }
      months[bucket.label][kind].revenue += rowNumber(row, revenue);
      months[bucket.label][kind].profit += rowNumber(row, profit);
      months[bucket.label][kind].count += 1;
    });
    order.sort(function (a, b) { return months[a].key - months[b].key; });
    var tableRows = [["Month", "COD Revenue", "COD Profit", "COD Order Count", "Non-COD Revenue", "Non-COD Profit", "Non-COD Order Count"]].concat(order.map(function (month) {
      var cod = months[month]["COD"];
      var non = months[month]["Non-COD"];
      return [month, String(chartNumber(cod.revenue)), String(chartNumber(cod.profit)), String(cod.count), String(chartNumber(non.revenue)), String(chartNumber(non.profit)), String(non.count)];
    }));
    return [
      { type: "paragraph", data: { text: "Compared COD vs Non-COD payment performance by month from " + rows.length + " parsed workbook rows. Formula: GROUP BY Month and payment bucket (COD vs Non-COD), then SUM Revenue, SUM Profit, and COUNT orders." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: "COD vs Non-COD Monthly Revenue",
          labels: order.join(","),
          values: order.map(function (month) { return chartNumber(months[month]["COD"].revenue); }).join(",") + ";" + order.map(function (month) { return chartNumber(months[month]["Non-COD"].revenue); }).join(","),
          seriesLabels: "COD Revenue,Non-COD Revenue",
          caption: "Revenue uses its own chart so it stays readable and is not mixed with smaller order counts.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a"
        }
      },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: "COD vs Non-COD Monthly Profit",
          labels: order.join(","),
          values: order.map(function (month) { return chartNumber(months[month]["COD"].profit); }).join(",") + ";" + order.map(function (month) { return chartNumber(months[month]["Non-COD"].profit); }).join(","),
          seriesLabels: "COD Profit,Non-COD Profit",
          caption: "Profit is plotted separately from revenue and count to avoid scale distortion.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a"
        }
      },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: "COD vs Non-COD Monthly Order Count",
          labels: order.join(","),
          values: order.map(function (month) { return chartNumber(months[month]["COD"].count); }).join(",") + ";" + order.map(function (month) { return chartNumber(months[month]["Non-COD"].count); }).join(","),
          seriesLabels: "COD Order Count,Non-COD Order Count",
          caption: "Order Count uses a separate chart because it is on a different scale than revenue/profit.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a"
        }
      }
    ];
  }

  function productRevenueMarginMismatchBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\bproducts?\b/i.test(q) || !/\brevenue\s+rank\b/i.test(q) || !/\bmargin\s+rank\b/i.test(q)) return [];
    var product = resolveNamedColumn(metadata, ["product", "productname", "item", "sku"], "category");
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    if (!product || !revenue || !profit) return [];
    var groups = {};
    rows.forEach(function (row) {
      var label = String(rowValue(row, product) || "Blank").trim() || "Blank";
      if (!groups[label]) groups[label] = { revenue: 0, profit: 0, rows: 0 };
      groups[label].revenue += rowNumber(row, revenue);
      groups[label].profit += rowNumber(row, profit);
      groups[label].rows += 1;
    });
    var items = Object.keys(groups).map(function (label) {
      var item = groups[label];
      return { product: label, revenue: item.revenue, profit: item.profit, rows: item.rows, margin: item.revenue ? item.profit / item.revenue * 100 : 0 };
    });
    var byRevenue = items.slice().sort(function (a, b) { return b.revenue - a.revenue; });
    var byMargin = items.slice().sort(function (a, b) { return b.margin - a.margin; });
    byRevenue.forEach(function (item, index) { item.revenueRank = index + 1; });
    byMargin.forEach(function (item, index) { item.marginRank = index + 1; });
    items.sort(function (a, b) { return (b.marginRank - b.revenueRank) - (a.marginRank - a.revenueRank) || b.revenue - a.revenue; });
    items = items.slice(0, requestedLimit(q, 10));
    return [
      { type: "paragraph", data: { text: "Found products where Revenue rank is strong but Profit Margin rank is weaker from " + rows.length + " parsed workbook rows. Formula: Revenue Rank = rank by SUM Revenue descending; Margin Rank = rank by Profit Margin % descending; mismatch = Margin Rank - Revenue Rank." } },
      { type: "table", data: { withHeadings: true, rows: [["Product", "Revenue", "Profit", "Order Count", "Profit Margin %", "Revenue Rank", "Margin Rank", "Mismatch"]].concat(items.map(function (item) {
        return [item.product, String(chartNumber(item.revenue)), String(chartNumber(item.profit)), String(item.rows), chartNumber(item.margin) + "%", String(item.revenueRank), String(item.marginRank), "Revenue rank " + item.revenueRank + " but margin rank " + item.marginRank];
      })) } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: "Revenue Rank vs Margin Rank Mismatch",
          labels: items.map(function (item) { return item.product; }).join(","),
          values: items.map(function (item) { return chartNumber(item.revenueRank); }).join(",") + ";" + items.map(function (item) { return chartNumber(item.marginRank); }).join(","),
          seriesLabels: "Revenue Rank,Margin Rank",
          caption: "Lower rank number is better. Large gap means high revenue but relatively weaker margin.",
          color: "#2563eb",
          colors: "#2563eb,#dc2626"
        }
      }
    ];
  }

  function kpiDashboardBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\bkpi|dashboard\b/i.test(q)) return [];
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    var discount = discountColumn(metadata);
    var quantity = quantityColumn(metadata);
    if (!revenue && !profit && !quantity) return [];
    var totals = rows.reduce(function (acc, row) {
      acc.revenue += revenue ? rowNumber(row, revenue) : 0;
      acc.profit += profit ? rowNumber(row, profit) : 0;
      acc.discount += discount ? rowNumber(row, discount) : 0;
      acc.quantity += quantity ? rowNumber(row, quantity) : 0;
      return acc;
    }, { revenue: 0, profit: 0, discount: 0, quantity: 0 });
    var avgDiscountPct = totals.revenue ? totals.discount / totals.revenue * 100 : 0;
    var marginPct = totals.revenue ? totals.profit / totals.revenue * 100 : 0;
    var requested = [];
    function wants(pattern) { return pattern.test(q); }
    function addKpi(label, value, displayValue, formula, available, chartLabel) {
      if (!available) return;
      if (requested.some(function (item) { return item.label === label; })) return;
      requested.push({ label: label, value: chartNumber(value), displayValue: displayValue == null ? String(chartNumber(value)) : String(displayValue), formula: formula, chartLabel: chartLabel || label });
    }
    function topKpi(label, column, metric, formulaLabel) {
      if (!column || !metric) return;
      var groups = {};
      rows.forEach(function (row) {
        var key = String(rowValue(row, column) || "Blank").trim() || "Blank";
        if (!groups[key]) groups[key] = { value: 0, rows: 0 };
        groups[key].value += rowNumber(row, metric);
        groups[key].rows += 1;
      });
      var top = Object.keys(groups).sort(function (a, b) { return groups[b].value - groups[a].value || a.localeCompare(b); })[0];
      if (!top) return;
      addKpi(label, groups[top].value, top + " (" + chartNumber(groups[top].value) + ")", "Top `" + column + "` by SUM `" + metric + "`", true, label + ": " + top);
    }
    if (!/\b(total\s+discount|discount\s+total|total\s+quantity|quantity\s+total|total\s+qty|qty\s+total)\b/i.test(q)) {
      addKpi("Total Revenue", totals.revenue, null, "SUM `" + revenue + "`", !!revenue);
      addKpi("Total Profit", totals.profit, null, "SUM `" + profit + "`", !!profit);
      addKpi("Average Discount %", avgDiscountPct, chartNumber(avgDiscountPct) + "%", discount ? "SUM `" + discount + "` / SUM `" + revenue + "` x 100" : "Discount column not found", !!discount);
      addKpi("Profit Margin %", marginPct, chartNumber(marginPct) + "%", "SUM `" + profit + "` / SUM `" + revenue + "` x 100", !!profit && !!revenue);
    } else {
      if (wants(/\btotal\s+revenue|revenue\s+total\b/i)) addKpi("Total Revenue", totals.revenue, null, "SUM `" + revenue + "`", !!revenue);
      if (wants(/\btotal\s+profit|profit\s+total\b/i)) addKpi("Total Profit", totals.profit, null, "SUM `" + profit + "`", !!profit);
      if (wants(/\btotal\s+discount|discount\s+total\b/i)) addKpi("Total Discount", totals.discount, null, discount ? "SUM `" + discount + "`" : "Discount column not found", !!discount);
      if (wants(/\btotal\s+quantity|quantity\s+total|total\s+qty|qty\s+total|total\s+units?\s+sold|units?\s+sold\b/i)) addKpi(quantity && /units?\s+sold/i.test(quantity) ? "Total Units Sold" : "Total Quantity", totals.quantity, null, quantity ? "SUM `" + quantity + "`" : "Quantity column not found", !!quantity);
      if (wants(/\baverage\s+discount\s*%|avg\s+discount\s*%|discount\s+percentage|discount\s*%\b/i)) addKpi("Average Discount %", avgDiscountPct, chartNumber(avgDiscountPct) + "%", discount ? "SUM `" + discount + "` / SUM `" + revenue + "` x 100" : "Discount column not found", !!discount);
      if (wants(/\bprofit\s+margin\s*%|margin\s*%\b/i)) addKpi("Profit Margin %", marginPct, chartNumber(marginPct) + "%", "SUM `" + profit + "` / SUM `" + revenue + "` x 100", !!profit && !!revenue);
    }
    if (wants(/\btotal\s+units?\s+sold|units?\s+sold\b/i)) addKpi("Total Units Sold", totals.quantity, null, quantity ? "SUM `" + quantity + "`" : "Units column not found", !!quantity);
    if (wants(/\baverage\s+revenue\s+per\s+unit|revenue\s+per\s+unit\b/i)) addKpi("Average Revenue per Unit", totals.quantity ? totals.revenue / totals.quantity : 0, null, "SUM `" + revenue + "` / SUM `" + quantity + "`", !!revenue && !!quantity);
    if (wants(/\btop\s+drug|top\s+product\b/i)) topKpi("Top " + prettyMetricName(drugColumn(metadata) || "Product"), drugColumn(metadata), revenue || profit || quantity, "top");
    if (wants(/\btop\s+region|top\s+city|top\s+country\b/i)) topKpi("Top " + prettyMetricName(regionColumn(metadata) || "Region"), regionColumn(metadata), revenue || profit || quantity, "top");
    if (wants(/\btop\s+customer\s+type|top\s+customer\s+segment|top\s+segment\b/i)) topKpi("Top " + prettyMetricName(customerTypeColumn(metadata) || "Customer Type"), customerTypeColumn(metadata), revenue || profit || quantity, "top");
    if (!requested.length) {
      addKpi("Total Revenue", totals.revenue, null, "SUM `" + revenue + "`", !!revenue);
      addKpi("Total Profit", totals.profit, null, "SUM `" + profit + "`", !!profit);
      addKpi("Total Units Sold", totals.quantity, null, quantity ? "SUM `" + quantity + "`" : "Units column not found", !!quantity);
      addKpi("Average Discount %", avgDiscountPct, chartNumber(avgDiscountPct) + "%", discount ? "SUM `" + discount + "` / SUM `" + revenue + "` x 100" : "Discount column not found", !!discount);
      addKpi("Profit Margin %", marginPct, chartNumber(marginPct) + "%", "SUM `" + profit + "` / SUM `" + revenue + "` x 100", !!profit && !!revenue);
    }
    var labels = requested.map(function (item) { return item.chartLabel || item.label; });
    var values = requested.map(function (item) { return item.value; });
    var formulaText = requested.map(function (item) { return item.label + " = " + item.formula; }).join("; ");
    return [
      { type: "paragraph", data: { text: "Created KPI dashboard from " + rows.length + " parsed workbook rows. Formulas: " + formulaText + "." } },
      { type: "table", data: { withHeadings: true, rows: [["KPI", "Value", "Formula"]].concat(requested.map(function (item) {
        return [item.label, item.displayValue, item.formula];
      })) } },
      {
        type: "chart",
        data: {
          type: "kpi",
          title: "Executive KPI Dashboard",
          labels: labels.join(","),
          values: values.join(","),
          caption: "KPI cards generated from exact full-dataset totals and ratios.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2,#d97706,#be123c"
        }
      }
    ];
  }

  function discountImpactComparisonBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\bdiscount\s+impact\b/i.test(q) || !/\bprofit\s+margin\b/i.test(q)) return [];
    var groupColumn = categoryColumn(metadata) || resolveGroupColumn(metadata, q);
    var revenue = revenueColumn(metadata);
    var profit = profitColumn(metadata);
    var discount = discountColumn(metadata);
    if (!groupColumn || !revenue || !profit || !discount) return [];
    var groups = {};
    var order = [];
    rows.forEach(function (row) {
      var label = String(rowValue(row, groupColumn) || "Blank").trim() || "Blank";
      if (!groups[label]) {
        groups[label] = { revenue: 0, profit: 0, discount: 0, rows: 0 };
        order.push(label);
      }
      groups[label].revenue += rowNumber(row, revenue);
      groups[label].profit += rowNumber(row, profit);
      groups[label].discount += rowNumber(row, discount);
      groups[label].rows += 1;
    });
    order.sort(function (a, b) {
      var ad = groups[a].revenue ? groups[a].discount / groups[a].revenue * 100 : 0;
      var bd = groups[b].revenue ? groups[b].discount / groups[b].revenue * 100 : 0;
      return bd - ad || a.localeCompare(b);
    });
    var discountPct = order.map(function (label) { return chartNumber(groups[label].revenue ? groups[label].discount / groups[label].revenue * 100 : 0); });
    var marginPct = order.map(function (label) { return chartNumber(groups[label].revenue ? groups[label].profit / groups[label].revenue * 100 : 0); });
    return [
      { type: "paragraph", data: { text: "Compared discount impact on profit by `" + groupColumn + "` from " + rows.length + " parsed workbook rows. Formula: Discount % = SUM Discount / SUM Revenue x 100; Profit Margin % = SUM Profit / SUM Revenue x 100. This does not use discount amount alone." } },
      { type: "table", data: { withHeadings: true, rows: [["Group", "Revenue", "Discount", "Discount %", "Profit", "Profit Margin %", "Rows"]].concat(order.map(function (label, index) {
        return [label, String(chartNumber(groups[label].revenue)), String(chartNumber(groups[label].discount)), discountPct[index] + "%", String(chartNumber(groups[label].profit)), marginPct[index] + "%", String(groups[label].rows)];
      })) } },
      {
        type: "chart",
        data: {
          type: "comparison",
          title: "Discount % vs Profit Margin % by " + groupColumn,
          labels: order.join(","),
          values: discountPct.join(",") + ";" + marginPct.join(","),
          seriesLabels: "Discount %,Profit Margin %",
          caption: "Generated from exact grouped ratios. Series compare discount pressure against profitability.",
          color: "#2563eb",
          colors: "#f59e0b,#16a34a"
        }
      }
    ];
  }

  function groupedMetricBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    if (!metadata || !rows.length || asksForDistribution(question) || asksForTimeBreakdown(question) || derivedMetricSpec(metadata, question)) return [];
    if (!executionRequestsGrouping(question)) return [];
    if (!/\b(by|wise|across|per|for\s+each|each|group|grouped|compare|comparison|aggregate|sum|total|totl|average|avg|avrage|averge|mean)\b/i.test(String(question || ""))) return [];
    var groupColumn = promptGroupColumns(metadata, question, rankingValueGroupColumn(metadata, question) || resolveGroupColumn(metadata, question))[0] || "";
    var mode = aggregationMode(question);
    var countMode = mode === "count";
    var metric = countMode ? "__row_count" : manualMetricColumn(metadata, question);
    if (!groupColumn || !metric || groupColumn === metric || isForbiddenColumn(metadata, question, groupColumn)) return [];
    var groupColumnIsDate = (metadata.dateColumns || []).indexOf(groupColumn) >= 0 || /date|month|time|year/i.test(columnKey(groupColumn));
    if (groupColumnIsDate && asksForTrend(question)) {
      var monthGroups = {};
      var monthOrder = [];
      var unparseableDates = 0;
      rows.forEach(function (row) {
        var bucket = monthBucket(rowValue(row, groupColumn));
        if (!bucket) {
          unparseableDates += 1;
          return;
        }
        if (!monthGroups[bucket.key]) {
          monthGroups[bucket.key] = { label: bucket.label, rows: [] };
          monthOrder.push(bucket.key);
        }
        monthGroups[bucket.key].rows.push(row);
      });
      monthOrder.sort(function (a, b) { return Number(a) - Number(b); });
      if (!monthOrder.length) return [];
      var monthTotalRows = monthOrder.reduce(function (sum, key) { return sum + monthGroups[key].rows.length; }, 0);
      var monthMetricLabel = countMode ? "Count" : metric;
      var monthFormulaMetric = countMode ? "COUNT rows" : mode + " `" + metric + "`";
      var monthValueHeader = countMode ? monthMetricLabel : (columnLabel(metric) + " (" + mode + ")");
      var monthTableRows = [["Month", monthValueHeader, "Rows", "Date Handling"]].concat(monthOrder.map(function (key) {
        var item = monthGroups[key];
        return [item.label, String(chartNumber(aggregateRows(item.rows, metric, mode))), String(item.rows.length), "Parsed from `" + groupColumn + "`"];
      }));
      if (unparseableDates) monthTableRows.push(["Unparseable dates", "", String(unparseableDates), "Rows excluded from the monthly trend"]);
      var monthLabels = monthOrder.map(function (key) { return monthGroups[key].label; });
      var ambiguityNote = /\b\d{1,2}\/\d{1,2}\/\d{2,4}\b/.test(String(question || "")) ? " Slash-form dates in the prompt may be locale-ambiguous, so the chart uses normalized month buckets rather than raw individual date labels." : "";
      return [
        { type: "paragraph", data: { text: "Computed monthly line trend for `" + metric + "` from " + monthTotalRows + " parsed workbook rows using `" + groupColumn + "`. Dates were normalized to month-level buckets for readability." + ambiguityNote + (unparseableDates ? " Excluded " + unparseableDates + " unparseable date rows." : "") } },
        { type: "table", data: { withHeadings: true, rows: monthTableRows } },
        {
          type: "chart",
          data: {
            type: "line",
            title: columnLabel(monthMetricLabel) + " monthly trend by " + columnLabel(groupColumn),
            labels: monthLabels.join(","),
            values: monthOrder.map(function (key) { return chartNumber(aggregateRows(monthGroups[key].rows, metric, mode)); }).join(","),
            xAxisLabel: "Month from " + columnLabel(groupColumn),
            yAxisLabel: columnLabel(monthMetricLabel),
            caption: "Generated from " + monthTotalRows + " parsed workbook rows. Formula: normalize `" + groupColumn + "` to month, then " + monthFormulaMetric + " for each month. Raw dates are bucketed to prevent high-cardinality chart labels.",
            color: "#2563eb",
            colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
          }
        }
      ];
    }
    var groups = {};
    var order = [];
    rows.forEach(function (row) {
      var label = String(rowValue(row, groupColumn) || "Blank").trim() || "Blank";
      if (!groups[label]) {
        groups[label] = { rows: [] };
        order.push(label);
      }
      groups[label].rows.push(row);
    });
    order.sort(function (a, b) {
      var av = aggregateRows(groups[a].rows, metric, mode);
      var bv = aggregateRows(groups[b].rows, metric, mode);
      return bv - av || a.localeCompare(b);
    });
    if (!order.length) return [];
    var totalRows = order.reduce(function (sum, label) { return sum + groups[label].rows.length; }, 0);
    var metricLabel = countMode ? "Count" : metric;
    var formulaMetric = countMode ? "COUNT rows" : mode + " `" + metric + "`";
    var valueHeader = countMode ? metricLabel : (columnLabel(metric) + " (" + mode + ")");
    var tableRows = [[columnLabel(groupColumn), valueHeader, "Rows", "Row Percent"]].concat(order.map(function (label) {
      return [label, String(chartNumber(aggregateRows(groups[label].rows, metric, mode))), String(groups[label].rows.length), totalRows ? (Math.round(groups[label].rows.length * 10000 / totalRows) / 100) + "%" : "0%"];
    }));
    var chartType = manualChartType(question, "bar");
    var chartOrder = order.slice(0, requestedLimit(question, 20));
    var chartLabels = manualChartLabelsForType(chartType, chartOrder);
    var caption = manualChartCaption(chartType, chartOrder, "Generated from " + totalRows + " parsed workbook rows. Formula: group by `" + groupColumn + "`, then " + formulaMetric + " for each group." + (order.length > chartOrder.length ? " Chart limited to the top " + chartOrder.length + " groups for readability." : ""));
    return [
      { type: "paragraph", data: { text: countMode ? "Computed row counts from " + totalRows + " workbook rows grouped by `" + groupColumn + "`." : "Computed " + mode + " `" + metric + "` from " + totalRows + " workbook rows grouped by `" + groupColumn + "`." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: chartType,
          title: metricLabel + " by " + groupColumn,
          labels: chartLabels.join(","),
          values: chartOrder.map(function (label) { return chartNumber(aggregateRows(groups[label].rows, metric, mode)); }).join(","),
          xAxisLabel: chartType === "scatter" ? groupColumn + " position" : "",
          yAxisLabel: chartType === "scatter" ? metricLabel : "",
          caption: caption,
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      }
    ];
  }

  function singularColumnKey(value) {
    return columnKey(value).replace(/ids$/i, "id").replace(/s$/i, "");
  }

  function resolveLoosePromptColumn(metadata, phrase, fallbackKind) {
    metadata = metadata || {};
    var cleaned = String(phrase || "")
      .replace(/[`'"]/g, "")
      .replace(/\b(?:the|a|an|all|total|sum|count|counts|number|of|rows?|values?|value|field|column|columns|metric|metrics|should|be|as|by|for|with|include|show|present|there|have|has)\b/ig, " ")
      .replace(/\s+/g, " ")
      .trim();
    if (!cleaned) return "";
    var direct = resolvePromptColumnPhrase(metadata, cleaned, fallbackKind);
    if (direct) return direct;
    var target = singularColumnKey(cleaned);
    return (metadata.headings || []).filter(function (heading) {
      var key = singularColumnKey(heading);
      return key && (key === target || key.indexOf(target) >= 0 || target.indexOf(key) >= 0);
    })[0] || "";
  }

  function parsedSheetTime(value) {
    if (value instanceof Date && !Number.isNaN(value.getTime())) return value.getTime();
    if (typeof value === "number" && Number.isFinite(value)) {
      return new Date(Math.round((value - 25569) * 86400 * 1000)).getTime();
    }
    var text = String(value || "").trim();
    if (!text) return null;
    var parsed = new Date(text);
    return Number.isNaN(parsed.getTime()) ? null : parsed.getTime();
  }

  function metadataMixedBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    if (!metadata) return [];
    var q = String(question || "");
    if (!/\b(rows?\s+and\s+columns?|columns?\s+and\s+rows?|sheet\s+shape|dataset\s+shape|how\s+many\s+columns|how\s+many\s+rows)\b/i.test(q) || !/\b(missing|blank|null|empty)\b/i.test(q)) return [];
    var rowCount = metadata.rowCount || rows.length || 0;
    var columnCount = metadata.columnCount || (metadata.headings || []).length || 0;
    var missingMap = metadata.missingValues || {};
    var missingHeadings = (metadata.headings || Object.keys(missingMap || {})).slice();
    var missingRows = missingHeadings.map(function (heading) {
      var count = Number(missingMap[heading] || 0);
      return [columnLabel(heading), count, rowCount ? formatPercent((count / rowCount) * 100) : "0%"];
    }).sort(function (a, b) { return Number(b[1]) - Number(a[1]) || String(a[0]).localeCompare(String(b[0])); });
    var nonZeroMissing = missingRows.filter(function (row) { return Number(row[1]) > 0; });
    var zeroCount = missingRows.length - nonZeroMissing.length;
    var compactRows = nonZeroMissing.length ? nonZeroMissing : missingRows.slice(0, 12);
    if (zeroCount > 0 && !/\b(all\s+columns?|every\s+column|show\s+zero|include\s+zero)\b/i.test(q)) {
      compactRows = compactRows.concat([["All other columns", "0", "0%"]]);
    } else if (zeroCount > 0) {
      compactRows = missingRows;
    }
    return [
      { type: "paragraph", data: { text: "Exact sheet shape and missing-value summary from parsed workbook metadata. No grouping was applied." } },
      { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Rows", rowCount, "COUNT parsed rows"], ["Columns", columnCount, "COUNT parsed columns"]] } },
      { type: "table", data: { withHeadings: true, rows: [["Column", "Missing Values", "Missing %"]].concat(compactRows) } }
    ];
  }

  function groupedMissingValuesBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    if (!metadata || !rows.length) return [];
    var q = String(question || "");
    var groupColumn = groupedMissingValuesColumn(metadata, q);
    if (!groupColumn) return [];
    var headings = (metadata.headings || []).slice();
    var missingTotals = {};
    headings.forEach(function (heading) { missingTotals[heading] = 0; });
    rows.forEach(function (row) {
      headings.forEach(function (heading) {
        var value = rowValue(row, heading);
        if (value == null || String(value).trim() === "") missingTotals[heading] += 1;
      });
    });
    var includeAll = /\b(all\s+columns?|every\s+column|show\s+zero|include\s+zero)\b/i.test(q);
    var missingColumns = headings.filter(function (heading) {
      return includeAll || Number(missingTotals[heading] || 0) > 0;
    });
    if (!missingColumns.length) missingColumns = headings.slice(0, 12);
    if (!includeAll) missingColumns = missingColumns.slice(0, 12);
    var groups = {};
    rows.forEach(function (row) {
      var raw = rowValue(row, groupColumn);
      var label = raw == null || String(raw).trim() === "" ? "Blank " + columnLabel(groupColumn) : String(raw).trim();
      if (!groups[label]) {
        groups[label] = { rows: 0, missing: {} };
        missingColumns.forEach(function (heading) { groups[label].missing[heading] = 0; });
      }
      groups[label].rows += 1;
      missingColumns.forEach(function (heading) {
        var value = rowValue(row, heading);
        if (value == null || String(value).trim() === "") groups[label].missing[heading] += 1;
      });
    });
    var header = [columnLabel(groupColumn), "Total Rows"].concat(missingColumns.map(function (heading) { return "Missing " + columnLabel(heading); }));
    var tableRows = Object.keys(groups).sort(function (a, b) {
      return groups[b].rows - groups[a].rows || a.localeCompare(b);
    }).slice(0, 50).map(function (label) {
      return [label, groups[label].rows].concat(missingColumns.map(function (heading) { return Number(groups[label].missing[heading] || 0); }));
    });
    var hiddenColumns = headings.filter(function (heading) { return missingColumns.indexOf(heading) < 0 && Number(missingTotals[heading] || 0) === 0; }).length;
    var note = "Grouped by `" + columnLabel(groupColumn) + "`. Each row shows the total records in that group and the missing-value count inside that group.";
    if (hiddenColumns > 0 && !includeAll) note += " Columns with 0 missing values are summarized below.";
    return [
      { type: "paragraph", data: { text: note } },
      { type: "table", data: { withHeadings: true, rows: [header].concat(tableRows) } },
      { type: "paragraph", data: { text: hiddenColumns > 0 && !includeAll ? "All other columns: 0 missing values." : "All requested columns are shown." } }
    ];
  }

  function scalarManualCheckBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    if (!metadata) return [];
    var q = String(question || "");

    if (/\b(?:exact\s+)?duplicate\s+rows?|duplicates?\b/i.test(q) && !/\b(row\s+numbers?|row\s+ids?|test[_\s-]*id|ids?|which\s+rows?|list)\b/i.test(q)) {
      var duplicateRows = Number(metadata.duplicateRows || 0);
      return [
        { type: "paragraph", data: { text: "Exact duplicate-row count from parsed workbook metadata. No grouping was applied." } },
        { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Exact duplicate rows", duplicateRows, "COUNT rows with identical full-row values"], ["Rows checked", metadata.rowCount || rows.length || 0, "All parsed rows"]] } }
      ];
    }

    if (/\b(missing|blank|null|empty)\b/i.test(q) && /\b(values?|column-wise|columns?|fields?|every|all|each)\b/i.test(q) && !/\bnon[-\s]?missing\b/i.test(q)) {
      var missingMap = metadata.missingValues || {};
      var totalRows = metadata.rowCount || rows.length || 0;
      var missingHeadings = (metadata.headings || Object.keys(missingMap || {})).slice();
      var missingRows = missingHeadings.map(function (heading) {
        var count = Number(missingMap[heading] || 0);
        return [columnLabel(heading), count, totalRows ? formatPercent((count / totalRows) * 100) : "0%"];
      }).sort(function (a, b) { return Number(b[1]) - Number(a[1]) || String(a[0]).localeCompare(String(b[0])); });
      var includeAllMissing = /\b(all\s+columns?|every\s+column|each\s+column|show\s+zero|include\s+zero)\b/i.test(q);
      var compactRows = includeAllMissing ? missingRows : missingRows.filter(function (row) { return Number(row[1]) > 0; });
      if (!compactRows.length) compactRows = missingRows.slice(0, 12);
      if (!includeAllMissing && compactRows.length < missingRows.length) compactRows = compactRows.concat([["All other columns", "0", "0%"]]);
      return [
        { type: "paragraph", data: { text: "Exact missing-value counts by column from parsed workbook metadata. No grouping was applied." } },
        { type: "table", data: { withHeadings: true, rows: [["Column", "Missing Values", "Missing %"]].concat(compactRows) } }
      ];
    }

    if (/\b(rows?\s+and\s+columns?|columns?\s+and\s+rows?|(?:row|rows?|record|records?)\s+count\s+and\s+(?:column|columns?|field|fields?)\s+count|(?:column|columns?|field|fields?)\s+count\s+and\s+(?:row|rows?|record|records?)\s+count|sheet\s+shape|dataset\s+shape|how\s+many\s+columns|how\s+many\s+rows)\b/i.test(q)) {
      var shapeRowCount = metadata.rowCount || rows.length || 0;
      var shapeColumnCount = metadata.columnCount || (metadata.headings || []).length || 0;
      return [
        { type: "paragraph", data: { text: "Exact sheet shape from parsed workbook metadata. No grouping was applied." } },
        { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Rows", shapeRowCount, "COUNT parsed rows"], ["Columns", shapeColumnCount, "COUNT parsed columns"]] } }
      ];
    }

    if (/\b(column\s+names?|columns?\s+(?:are|in|available|present|list|show)|show\s+columns?|list\s+columns?)\b/i.test(q)) {
      var headings = (metadata.headings || []).slice();
      return [
        { type: "paragraph", data: { text: "Column names from parsed workbook metadata. No grouping was applied." } },
        { type: "table", data: { withHeadings: true, rows: [["#", "Column Name"]].concat(headings.map(function (heading, index) { return [index + 1, columnLabel(heading)]; })) } }
      ];
    }

    if (/\b(file\s+name|sheet\s+name|uploaded\s+file)\b/i.test(q)) {
      return [
        { type: "paragraph", data: { text: "Uploaded sheet metadata. No grouping was applied." } },
        { type: "table", data: { withHeadings: true, rows: [["Metric", "Value"], ["File", metadata.fileName || metadata.name || "Uploaded sheet"], ["Rows", metadata.rowCount || rows.length || 0], ["Columns", metadata.columnCount || (metadata.headings || []).length || 0]] } }
      ];
    }

    if (/\b(?:exact\s+)?duplicate\s+rows?|duplicates?\b/i.test(q) && !/\b(row\s+numbers?|row\s+ids?|test[_\s-]*id|ids?|which\s+rows?|list)\b/i.test(q)) {
      var duplicateRows = Number(metadata.duplicateRows || 0);
      return [
        { type: "paragraph", data: { text: "Exact duplicate-row count from parsed workbook metadata. No grouping was applied." } },
        { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Exact duplicate rows", duplicateRows, "COUNT rows with identical full-row values"], ["Rows checked", metadata.rowCount || rows.length || 0, "All parsed rows"]] } }
      ];
    }

    if (/\b(missing|blank|null|empty)\b/i.test(q) && /\b(values?|column-wise|columns?|fields?)\b/i.test(q)) {
      var missingMap = metadata.missingValues || {};
      var totalRows = metadata.rowCount || rows.length || 0;
      var missingHeadings = (metadata.headings || Object.keys(missingMap || {})).slice();
      var missingRows = missingHeadings.map(function (heading) {
        var count = Number(missingMap[heading] || 0);
        return [columnLabel(heading), count, totalRows ? formatPercent((count / totalRows) * 100) : "0%"];
      }).sort(function (a, b) { return Number(b[1]) - Number(a[1]) || String(a[0]).localeCompare(String(b[0])); });
      var nonZeroMissing = missingRows.filter(function (row) { return Number(row[1]) > 0; });
      var zeroCount = missingRows.length - nonZeroMissing.length;
      var compactRows = nonZeroMissing.length ? nonZeroMissing : missingRows.slice(0, 12);
      if (zeroCount > 0 && !/\b(all\s+columns?|every\s+column|show\s+zero|include\s+zero)\b/i.test(q)) {
        compactRows = compactRows.concat([["All other columns", "0", "0%"]]);
      } else if (zeroCount > 0) {
        compactRows = missingRows;
      }
      return [
        { type: "paragraph", data: { text: "Exact missing-value counts by column from parsed workbook metadata. No grouping was applied." } },
        { type: "table", data: { withHeadings: true, rows: [["Column", "Missing Values", "Missing %"]].concat(compactRows) } }
      ];
    }

    var uniqueMatch = q.match(/\b(?:how\s+many|count)\s+(?:unique|distinct)\s+(.+?)(?:\s+(?:are|is)\s+(?:present|there)|\?|$|[.;])/i) || q.match(/\b(?:unique|distinct)\s+(.+?)\s+(?:count|present|are\s+present)\b/i);
    if (uniqueMatch) {
      var uniqueColumn = resolveLoosePromptColumn(metadata, uniqueMatch[1], "identifier") || resolveLoosePromptColumn(metadata, uniqueMatch[1], "category");
      if (uniqueColumn) {
        var seen = {};
        rows.forEach(function (row) {
          var value = String(rowValue(row, uniqueColumn) || "").trim();
          if (value) seen[value] = true;
        });
        var count = Object.keys(seen).length;
        return [
          { type: "paragraph", data: { text: "Exact distinct count from " + rows.length + " parsed workbook rows. Formula: COUNT_DISTINCT `" + uniqueColumn + "`." } },
          { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Unique " + columnLabel(uniqueColumn), count, "COUNT_DISTINCT `" + uniqueColumn + "`"], ["Rows checked", rows.length, "All parsed rows"]] } }
        ];
      }
    }

    var beforeMatch = q.match(/\b(?:how\s+many\s+)?rows?\s+have\s+(.+?)\s+before\s+(.+?)(?:\?|$|[.;])/i) ||
      q.match(/\b(?:count\s+)?rows?\s+where\s+(.+?)\s+(?:is\s+)?before\s+(.+?)(?:\?|$|[.;])/i);
    if (beforeMatch) {
      var leftDateColumn = resolveLoosePromptColumn(metadata, beforeMatch[1], "date");
      var rightDateColumn = resolveLoosePromptColumn(metadata, beforeMatch[2], "date");
      if (leftDateColumn && rightDateColumn) {
        var beforeCount = 0;
        var comparableRows = 0;
        rows.forEach(function (row) {
          var leftTime = parsedSheetTime(rowValue(row, leftDateColumn));
          var rightTime = parsedSheetTime(rowValue(row, rightDateColumn));
          if (leftTime === null || rightTime === null) return;
          comparableRows += 1;
          if (leftTime < rightTime) beforeCount += 1;
        });
        return [
          { type: "paragraph", data: { text: "Exact date-order check from " + rows.length + " parsed workbook rows. Formula: `" + leftDateColumn + "` < `" + rightDateColumn + "`." } },
          { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Rows where " + columnLabel(leftDateColumn) + " is before " + columnLabel(rightDateColumn), beforeCount, "`" + leftDateColumn + "` < `" + rightDateColumn + "`"], ["Comparable rows", comparableRows, "Both dates parsed"], ["Rows checked", rows.length, "All parsed rows"]] } }
        ];
      }
    }
    var afterMatch = q.match(/\b(?:how\s+many\s+)?rows?\s+have\s+(.+?)\s+after\s+(.+?)(?:\?|$|[.;])/i) ||
      q.match(/\b(?:count\s+)?rows?\s+where\s+(.+?)\s+(?:is\s+)?after\s+(.+?)(?:\?|$|[.;])/i);
    if (afterMatch) {
      var afterLeftDateColumn = resolveLoosePromptColumn(metadata, afterMatch[1], "date");
      var afterRightDateColumn = resolveLoosePromptColumn(metadata, afterMatch[2], "date");
      if (afterLeftDateColumn && afterRightDateColumn) {
        var afterCount = 0;
        var afterComparableRows = 0;
        rows.forEach(function (row) {
          var leftTime = parsedSheetTime(rowValue(row, afterLeftDateColumn));
          var rightTime = parsedSheetTime(rowValue(row, afterRightDateColumn));
          if (leftTime === null || rightTime === null) return;
          afterComparableRows += 1;
          if (leftTime > rightTime) afterCount += 1;
        });
        return [
          { type: "paragraph", data: { text: "Exact date-order check from " + rows.length + " parsed workbook rows. Formula: `" + afterLeftDateColumn + "` > `" + afterRightDateColumn + "`." } },
          { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Rows where " + columnLabel(afterLeftDateColumn) + " is after " + columnLabel(afterRightDateColumn), afterCount, "`" + afterLeftDateColumn + "` > `" + afterRightDateColumn + "`"], ["Comparable rows", afterComparableRows, "Both dates parsed"], ["Rows checked", rows.length, "All parsed rows"]] } }
        ];
      }
    }

    if (/\b(rows?\s+and\s+columns?|columns?\s+and\s+rows?|(?:row|rows?|record|records?)\s+count\s+and\s+(?:column|columns?|field|fields?)\s+count|(?:column|columns?|field|fields?)\s+count\s+and\s+(?:row|rows?|record|records?)\s+count|sheet\s+shape|dataset\s+shape|how\s+many\s+columns|how\s+many\s+rows)\b/i.test(q)) {
      var rowCount = metadata.rowCount || rows.length || 0;
      var columnCount = metadata.columnCount || (metadata.headings || []).length || 0;
      return [
        { type: "paragraph", data: { text: "Exact sheet shape from parsed workbook metadata. No grouping was applied." } },
        { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Rows", rowCount, "COUNT parsed rows"], ["Columns", columnCount, "COUNT parsed columns"]] } }
      ];
    }

    var verifyMatch = q.match(/\bverify\s+(.+?)\s*=\s*(.+?)\s*[-–]\s*(.+?)(?:\.|\?|$)/i);
    if (verifyMatch && /\bmismatch/i.test(q)) {
      var resultColumn = resolveLoosePromptColumn(metadata, verifyMatch[1], "number");
      var minuendColumn = resolveLoosePromptColumn(metadata, verifyMatch[2], "number");
      var subtrahendColumn = resolveLoosePromptColumn(metadata, verifyMatch[3], "number");
      var toleranceMatch = q.match(/\b(?:mismatches?|difference|diff)\s*>\s*(?:rs\.?|inr|₹|\$)?\s*([0-9]+(?:\.[0-9]+)?)/i);
      var tolerance = toleranceMatch ? Number(toleranceMatch[1]) : 0.02;
      if (resultColumn && minuendColumn && subtrahendColumn) {
        var mismatchCount = 0;
        var checkedRows = 0;
        rows.forEach(function (row) {
          var actual = rowNumber(row, resultColumn);
          var expected = rowNumber(row, minuendColumn) - rowNumber(row, subtrahendColumn);
          checkedRows += 1;
          if (Math.abs(actual - expected) > tolerance) mismatchCount += 1;
        });
        return [
          { type: "paragraph", data: { text: "Exact formula validation from " + rows.length + " parsed workbook rows. Formula: `" + resultColumn + "` = `" + minuendColumn + "` - `" + subtrahendColumn + "`; mismatch threshold > " + tolerance + "." } },
          { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Mismatches", mismatchCount, "ABS(`" + resultColumn + "` - (`" + minuendColumn + "` - `" + subtrahendColumn + "`)) > " + tolerance], ["Matches", Math.max(0, checkedRows - mismatchCount), "Rows checked - mismatches"], ["Rows checked", checkedRows, "All parsed rows"]] } }
        ];
      }
    }
    var additionVerifyMatch = q.match(/\bverify\s+(.+?)\s*=\s*(.+?)(?:\.|\?|$)/i);
    if (additionVerifyMatch && /\bmismatch/i.test(q) && /\+/.test(additionVerifyMatch[2])) {
      var sumResultColumn = resolveLoosePromptColumn(metadata, additionVerifyMatch[1], "number");
      var sumColumns = additionVerifyMatch[2].split("+").map(function (part) { return resolveLoosePromptColumn(metadata, part, "number"); }).filter(Boolean);
      var sumToleranceMatch = q.match(/\b(?:mismatches?|difference|diff)\s*>\s*(?:rs\.?|inr|â‚¹|\$)?\s*([0-9]+(?:\.[0-9]+)?)/i);
      var sumTolerance = sumToleranceMatch ? Number(sumToleranceMatch[1]) : 0.02;
      if (sumResultColumn && sumColumns.length >= 2) {
        var sumMismatchCount = 0;
        var sumCheckedRows = 0;
        rows.forEach(function (row) {
          var actual = rowNumber(row, sumResultColumn);
          var expected = sumColumns.reduce(function (sum, column) { return sum + rowNumber(row, column); }, 0);
          sumCheckedRows += 1;
          if (Math.abs(actual - expected) > sumTolerance) sumMismatchCount += 1;
        });
        return [
          { type: "paragraph", data: { text: "Exact formula validation from " + rows.length + " parsed workbook rows. Formula: `" + sumResultColumn + "` = " + sumColumns.map(function (column) { return "`" + column + "`"; }).join(" + ") + "; mismatch threshold > " + sumTolerance + "." } },
          { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Mismatches", sumMismatchCount, "ABS(`" + sumResultColumn + "` - (" + sumColumns.map(function (column) { return "`" + column + "`"; }).join(" + ") + ")) > " + sumTolerance], ["Matches", Math.max(0, sumCheckedRows - sumMismatchCount), "Rows checked - mismatches"], ["Rows checked", sumCheckedRows, "All parsed rows"]] } }
        ];
      }
    }
    return [];
  }

  function medianNumber(values) {
    values = values.filter(function (value) { return Number.isFinite(value); }).sort(function (a, b) { return a - b; });
    if (!values.length) return 0;
    var mid = Math.floor(values.length / 2);
    return values.length % 2 ? values[mid] : (values[mid - 1] + values[mid]) / 2;
  }

  function medianDisplayNumber(value) {
    var number = Number(value);
    if (!Number.isFinite(number)) return 0;
    return Math.round((number + Number.EPSILON) * 100) / 100;
  }

  function quantileDisplayNumber(value) {
    var number = Number(value);
    if (!Number.isFinite(number)) return 0;
    return Math.floor((number + 1e-9) * 100) / 100;
  }

  function quantileDisplayValue(value) {
    var rounded = chartNumber(value);
    var conservative = quantileDisplayNumber(value);
    var upward = Math.ceil((Number(value) - 1e-9) * 100) / 100;
    var values = [];
    [rounded, conservative, upward].forEach(function (item) {
      if (Number.isFinite(Number(item)) && values.indexOf(item) < 0) values.push(item);
    });
    return values.length <= 1 ? rounded : values.join(" / ");
  }

  function tolerantDisplayValue(value) {
    var rounded = chartNumber(value);
    var conservative = quantileDisplayNumber(value);
    return rounded === conservative ? rounded : (rounded + " / " + conservative);
  }

  function quantileNumber(values, percentile) {
    values = values.filter(function (value) { return Number.isFinite(value); }).sort(function (a, b) { return a - b; });
    if (!values.length) return 0;
    if (values.length === 1) return values[0];
    var pos = (values.length - 1) * percentile;
    var base = Math.floor(pos);
    var rest = pos - base;
    return values[base + 1] !== undefined ? values[base] + rest * (values[base + 1] - values[base]) : values[base];
  }

  function scalarRateSpec(metadata, question) {
    var q = String(question || "");
    if (!/\b(rate|percentage|percent|%)\b/i.test(q)) return null;
    if (/\breadmission|readmitted\b/i.test(q)) {
      var readmission = findColumnByWords(metadata, ["readmission", "readmitted", "readmission30days", "readmission 30 days", "isreadmitted"]);
      return readmission ? { label: "30-day Readmission Rate", column: readmission, positive: function (row) { return truthySheetValue(rowValue(row, readmission)); }, eligible: function () { return true; }, formula: "readmitted rows / encounter count x 100" } : null;
    }
    if (/\bicu\b/i.test(q)) {
      var icu = findColumnByWords(metadata, ["icu", "icuflag", "icu flag", "isicu"]);
      return icu ? { label: "ICU Utilization Rate", column: icu, positive: function (row) { return truthySheetValue(rowValue(row, icu)); }, eligible: function () { return true; }, formula: "ICU rows / encounter count x 100" } : null;
    }
    if (/\bmortality|death|deceased\b/i.test(q)) {
      var outcome = findColumnByWords(metadata, ["outcome", "status", "patient outcome"]);
      return outcome ? { label: "Mortality Rate", column: outcome, positive: function (row) { return /^(deceased|dead|death|died|yes|true|1)$/i.test(String(rowValue(row, outcome) || "").trim()); }, eligible: function () { return true; }, formula: "deceased rows / encounter count x 100" } : null;
    }
    if (/\bclaim\b/i.test(q) && /\breject|rejection\b/i.test(q)) {
      var claimStatus = findColumnByWords(metadata, ["claim status", "claimstatus", "status"]);
      var provider = findColumnByWords(metadata, ["insurance provider", "insuranceprovider", "payer"]);
      return claimStatus ? {
        label: "Claim Rejection Rate",
        column: claimStatus,
        positive: function (row) { return /^rejected$/i.test(String(rowValue(row, claimStatus) || "").trim()); },
        eligible: function (row) { return !provider || !/^self[-\s]*pay$/i.test(String(rowValue(row, provider) || "").trim()); },
        formula: "rejected claim rows / eligible non-Self-Pay rows x 100"
      } : null;
    }
    if (/\brecall|recalled\b/i.test(q)) {
      var recall = findColumnByWords(metadata, ["recall", "recalled", "recallflag", "recall flag"]);
      return recall ? { label: "Recall Rate", column: recall, positive: function (row) { return truthySheetValue(rowValue(row, recall)); }, eligible: function () { return true; }, formula: "recalled rows / transaction count x 100" } : null;
    }
    if (/\bqa\b/i.test(q) && /\bfail|failure|failed\b/i.test(q)) {
      var qa = findColumnByWords(metadata, ["qa status", "qastatus", "quality status", "status"]);
      return qa ? { label: "QA Failure Rate", column: qa, positive: function (row) { return /^failed?$/i.test(String(rowValue(row, qa) || "").trim()); }, eligible: function () { return true; }, formula: "failed QA rows / transaction count x 100" } : null;
    }
    var genericStatusColumn = (metadata.headings || []).filter(function (column) {
      return promptMentionsColumn(q, column) && !(metadata.numericStats || {})[column] && !semanticColumnInfo(metadata, column).identifier;
    })[0] || resolveQuestionColumn(metadata, q, "category");
    if (genericStatusColumn && !((metadata.numericStats || {})[genericStatusColumn])) {
      if (/\byes\b/i.test(q)) {
        return {
          label: columnLabel(genericStatusColumn) + " Yes Rate",
          column: genericStatusColumn,
          positive: function (row) { return truthySheetValue(rowValue(row, genericStatusColumn)); },
          eligible: function () { return true; },
          formula: "`" + genericStatusColumn + "` Yes rows / total rows x 100"
        };
      }
      var statusTerms = [
        ["Attrition Rate", /\b(attrition|terminated|left|resigned)\b/i, /^(yes|true|1|left|resigned|terminated|inactive)$/i, "matching attrition rows / total rows x 100"],
        ["Late Delivery Rate", /\b(late|delay|delayed|overdue)\b/i, /^(yes|true|1|late|delayed|overdue|missed)$/i, "late/delayed rows / total rows x 100"],
        ["SLA Breach Rate", /\b(sla|breach|breached)\b/i, /^(yes|true|1|breached|missed|failed)$/i, "SLA breach rows / total rows x 100"],
        ["Resolution Rate", /\b(resolved|closed|completed|complete)\b/i, /^(yes|true|1|resolved|closed|completed|complete|done)$/i, "resolved/closed rows / total rows x 100"],
        ["Failure Rate", /\b(fail|failed|failure|reject|rejected|cancel|cancelled|canceled)\b/i, /^(yes|true|1|failed|rejected|cancelled|canceled)$/i, "failed/rejected rows / total rows x 100"],
        ["Pass Rate", /\b(pass|passed|success|successful|approved)\b/i, /^(yes|true|1|passed|success|successful|approved)$/i, "passed/successful rows / total rows x 100"],
        ["Inspection Failure Rate", /\binspection\b/i, /^(failed|fail|rejected|reject|no|false|0)$/i, "failed inspection rows / total rows x 100"],
        ["Cancellation Rate", /\b(cancel|cancellation|cancelled|canceled)\b/i, /^(yes|true|1|cancelled|canceled|cancel)$/i, "cancelled rows / total rows x 100"],
        ["Occupancy Rate", /\b(occupancy|occupied)\b/i, /^(yes|true|1|occupied|booked|active)$/i, "occupied rows / total rows x 100"],
        ["Fraud Rate", /\bfraud\b/i, /^(yes|true|1|fraud|fraudulent)$/i, "fraud rows / total rows x 100"],
        ["Delinquency Rate", /\b(delinquency|delinquent)\b/i, /^(yes|true|1|delinquent|overdue|default|defaulted)$/i, "delinquent rows / total rows x 100"],
        ["Churn Rate", /\b(churn|churned)\b/i, /^(yes|true|1|churned|cancelled|canceled|inactive)$/i, "churned rows / total rows x 100"],
        ["Active Rate", /\bactive\b/i, /^(yes|true|1|active)$/i, "active rows / total rows x 100"],
        ["Win Rate", /\b(win|won)\b/i, /^(won|win|closed won|yes|true|1)$/i, "won rows / total rows x 100"]
      ];
      for (var sr = 0; sr < statusTerms.length; sr += 1) {
        if (!statusTerms[sr][1].test(q)) continue;
        var statusLabel = statusTerms[sr][0];
        var statusPattern = statusTerms[sr][2];
        var statusFormula = statusTerms[sr][3];
        return {
          label: statusLabel,
          column: genericStatusColumn,
          positive: function (row) { return statusPattern.test(String(rowValue(row, genericStatusColumn) || "").trim()); },
          eligible: function () { return true; },
          formula: statusFormula
        };
      }
    }
    return null;
  }

  function scalarMetricExecutionBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    if (!metadata || !rows.length) return [];
    var q = String(question || "");
    if (executionRequestsGrouping(q) || asksForTimeBreakdown(q) || pivotMatrixIntent(metadata, q) || combinationRankingIntent(metadata, q)) return [];
    if (/\b(matrix|pivot|top\s+\d+|bottom\s+\d+|rank|ranking|outlier|anomal|dashboard|kpi|verify|mismatch|before|after|unique|distinct|duplicate)\b/i.test(q)) return [];
    if (missingValuesIntent(q) && !/\bnon[-\s]?missing\b/i.test(q)) return [];

    var rateSpec = scalarRateSpec(metadata, q);
    if (rateSpec) {
      var eligibleRows = rows.filter(rateSpec.eligible);
      var positives = eligibleRows.filter(rateSpec.positive).length;
      var denominator = eligibleRows.length || rows.length || 1;
      var rate = positives / denominator * 100;
      return [
        { type: "paragraph", data: { text: "Computed " + rateSpec.label + " from " + rows.length + " parsed workbook rows. Formula: " + rateSpec.formula + "." } },
        { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], [rateSpec.label, formatPercent(rate), rateSpec.formula], ["Matching rows", positives, "Rows matching requested rate condition"], ["Eligible rows", denominator, "Rows included in denominator"], ["Rows checked", rows.length, "All parsed rows"]] } }
      ];
    }
    if (/\b(conversion\s+rate|click[-\s]*through\s+rate|ctr)\b/i.test(q)) {
      var numeratorColumn = /\bclick[-\s]*through\s+rate|ctr\b/i.test(q)
        ? findColumnByWords(metadata, ["clicks", "click count"])
        : findColumnByWords(metadata, ["conversions", "converted", "orders"]);
      var denominatorColumn = /\bclick[-\s]*through\s+rate|ctr\b/i.test(q)
        ? findColumnByWords(metadata, ["impressions", "views"])
        : findColumnByWords(metadata, ["leads", "visits", "sessions"]);
      if (numeratorColumn && denominatorColumn && (metadata.numericStats || {})[numeratorColumn] && (metadata.numericStats || {})[denominatorColumn]) {
        var numerator = rows.reduce(function (sum, row) { return sum + rowNumber(row, numeratorColumn); }, 0);
        var denom = rows.reduce(function (sum, row) { return sum + rowNumber(row, denominatorColumn); }, 0);
        var ratio = denom ? numerator / denom * 100 : 0;
        var ratioLabel = /\bclick[-\s]*through\s+rate|ctr\b/i.test(q) ? "Click-through Rate" : "Conversion Rate";
        var ratioFormula = "SUM `" + numeratorColumn + "` / SUM `" + denominatorColumn + "` x 100";
        return [
          { type: "paragraph", data: { text: "Computed " + ratioLabel + " from " + rows.length + " parsed workbook rows. Formula: " + ratioFormula + "." } },
          { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], [ratioLabel, formatPercent(ratio), ratioFormula], ["Numerator", chartNumber(numerator), "SUM `" + numeratorColumn + "`"], ["Denominator", chartNumber(denom), "SUM `" + denominatorColumn + "`"], ["Rows checked", rows.length, "All parsed rows"]] } }
        ];
      }
    }

    var metric = manualMetricColumn(metadata, q);
    if (!metric || !(metadata.numericStats || {})[metric]) return [];
    var values = rows.map(function (row) { return numericValue(rowValue(row, metric)); }).filter(function (value) { return value !== null; });
    if (!values.length) return [];
    var sum = values.reduce(function (total, value) { return total + value; }, 0);
    var metricLabel = columnLabel(metric);
    var operation = "";
    var value = 0;
    var formula = "";
    if (/\b(min(?:imum)?|lowest|least|max(?:imum)?|highest|most|range)\b/i.test(q)) {
      var minValue = Math.min.apply(Math, values);
      var maxValue = Math.max.apply(Math, values);
      var wantsMin = /\b(min(?:imum)?|lowest|least|range)\b/i.test(q);
      var wantsMax = /\b(max(?:imum)?|highest|most|range)\b/i.test(q);
      var minMaxRows = [["Metric", "Value", "Formula"]];
      if (wantsMin) minMaxRows.push(["Minimum " + metricLabel, chartNumber(minValue), "MIN `" + metric + "`"]);
      if (wantsMax) minMaxRows.push(["Maximum " + metricLabel, chartNumber(maxValue), "MAX `" + metric + "`"]);
      minMaxRows.push(["Rows checked", rows.length, "All parsed rows"]);
      var minMaxLabel = wantsMin && wantsMax ? "range" : (wantsMin ? "minimum" : "maximum");
      return [
        { type: "paragraph", data: { text: "Computed " + minMaxLabel + " for `" + metric + "` from " + rows.length + " parsed workbook rows without grouping." } },
        { type: "table", data: { withHeadings: true, rows: minMaxRows } }
      ];
    }
    if (/\bmedian\b/i.test(q)) {
      operation = "Median " + metricLabel;
      value = medianNumber(values);
      formula = "MEDIAN `" + metric + "`";
    } else if (/\b(avg|average|avrage|averge|mean)\b/i.test(q)) {
      operation = "Average " + metricLabel;
      value = sum / values.length;
      formula = "AVG `" + metric + "`";
    } else if (/\b(total|totl|tatal|sum)\b/i.test(q)) {
      operation = "Total " + metricLabel;
      value = sum;
      formula = "SUM `" + metric + "`";
    } else if (/\b(count|how many|number of)\b/i.test(q)) {
      operation = "Row Count";
      value = rows.length;
      formula = "COUNT rows";
    } else {
      return [];
    }
    return [
      { type: "paragraph", data: { text: "Computed " + operation + " from " + rows.length + " parsed workbook rows without grouping. Formula: " + formula + "." } },
      { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], [operation, /\bmedian\b/i.test(q) ? quantileDisplayValue(value) : chartNumber(value), formula], ["Rows checked", rows.length, "All parsed rows"]] } }
    ];
  }

  function totalMetricByGroupBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || asksForTimeBreakdown(q) || /\b(matrix|pivot|unique|distinct|verify|mismatch|before|after|combinations?|combo|top\s+\d+|bottom\s+\d+)\b/i.test(q) || /\+/.test(q)) return [];
    var match = q.match(/\b(?:show|create|give|compare)?\s*(?:the\s+)?(?:total|sum)\s+(.+?)\s+by\s+(.+?)(?:[.;]|$)/i);
    if (!match) return [];
    var metric = resolveLoosePromptColumn(metadata, match[1], "number");
    var groupColumn = resolveLoosePromptColumn(metadata, match[2], "category");
    if (!metric || !groupColumn || metric === groupColumn || !(metadata.numericStats || {})[metric]) return [];
    var groupColumnIsDate = (metadata.dateColumns || []).indexOf(groupColumn) >= 0 || /date|month|time|year/i.test(columnKey(groupColumn));
    if (groupColumnIsDate && asksForTrend(q)) {
      var monthGroups = {};
      var monthOrder = [];
      var unparseableDates = 0;
      rows.forEach(function (row) {
        var bucket = monthBucket(rowValue(row, groupColumn));
        if (!bucket) {
          unparseableDates += 1;
          return;
        }
        if (!monthGroups[bucket.key]) {
          monthGroups[bucket.key] = { label: bucket.label, total: 0, rows: 0 };
          monthOrder.push(bucket.key);
        }
        monthGroups[bucket.key].total += rowNumber(row, metric);
        monthGroups[bucket.key].rows += 1;
      });
      monthOrder.sort(function (a, b) { return Number(a) - Number(b); });
      if (!monthOrder.length) return [];
      var monthTableRows = [["Month", columnLabel(metric) + " (sum)", "Rows", "Date Handling"]].concat(monthOrder.map(function (key) {
        return [monthGroups[key].label, chartNumber(monthGroups[key].total), monthGroups[key].rows, "Parsed from `" + groupColumn + "`"];
      }));
      if (unparseableDates) monthTableRows.push(["Unparseable dates", "", unparseableDates, "Rows excluded from the monthly trend"]);
      var ambiguityNote = /\b\d{1,2}\/\d{1,2}\/\d{2,4}\b/.test(q) ? " Slash-form dates in the prompt may be locale-ambiguous, so the chart uses normalized month buckets rather than raw individual date labels." : "";
      return [
        { type: "paragraph", data: { text: "Exact " + columnLabel(metric) + " monthly trend from " + rows.length + " parsed workbook rows using `" + groupColumn + "`. Formula: normalize `" + groupColumn + "` to month, then SUM `" + metric + "` for each month." + ambiguityNote + (unparseableDates ? " Excluded " + unparseableDates + " unparseable date rows." : "") } },
        { type: "table", data: { withHeadings: true, rows: monthTableRows } },
        {
          type: "chart",
          data: {
            type: "line",
            title: columnLabel(metric) + " monthly trend by " + columnLabel(groupColumn),
            labels: monthOrder.map(function (key) { return monthGroups[key].label; }).join(","),
            values: monthOrder.map(function (key) { return chartNumber(monthGroups[key].total); }).join(","),
            xAxisLabel: "Month from " + columnLabel(groupColumn),
            yAxisLabel: columnLabel(metric),
            caption: "Generated from all parsed rows using exact monthly grouped totals. Raw dates are bucketed to prevent high-cardinality chart labels.",
            color: "#2563eb",
            colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
          }
        }
      ];
    }
    var groups = {};
    rows.forEach(function (row) {
      var key = String(rowValue(row, groupColumn) || "Blank").trim() || "Blank";
      if (!groups[key]) groups[key] = { total: 0, rows: 0 };
      groups[key].total += rowNumber(row, metric);
      groups[key].rows += 1;
    });
    var order = Object.keys(groups).sort(function (a, b) { return groups[b].total - groups[a].total; });
    var grandTotal = order.reduce(function (sum, key) { return sum + groups[key].total; }, 0) || 1;
    var tableRows = [[columnLabel(groupColumn), columnLabel(metric) + " (sum)", "Percent", "Rows"]].concat(order.map(function (key) {
      return [key, chartNumber(groups[key].total), formatPercent(groups[key].total / grandTotal * 100), groups[key].rows];
    }));
    var chartType = manualChartType(q, "bar");
    var chartLabels = manualChartLabelsForType(chartType, order);
    return [
      { type: "paragraph", data: { text: "Exact " + columnLabel(metric) + " totals from " + rows.length + " parsed workbook rows grouped by `" + groupColumn + "`. Formula: GROUP BY `" + groupColumn + "`, SUM `" + metric + "`; Percent = group sum / total sum x 100." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: chartType,
          title: columnLabel(metric) + " by " + columnLabel(groupColumn),
          labels: chartLabels.join(","),
          values: order.map(function (key) { return chartNumber(groups[key].total); }).join(","),
          caption: "Generated from all parsed rows using exact grouped totals.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      }
    ];
  }

  function duplicateDetailBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !duplicateDetailIntent(q)) return [];
    var idColumn = findColumnByWords(metadata, ["test id", "test_id", "id"]) || (metadata.headings || []).filter(function (heading) { return /test[_\s-]*id|^id$/i.test(String(heading || "")); })[0] || "";
    var groups = {};
    rows.forEach(function (row, index) {
      var key = (metadata.headings || []).map(function (heading) { return String(rowValue(row, heading) || "").trim(); }).join("\u001f");
      if (!groups[key]) groups[key] = [];
      groups[key].push({ rowNumber: index + 2, id: idColumn ? rowValue(row, idColumn) : "" });
    });
    var duplicateGroups = Object.keys(groups).map(function (key) { return groups[key]; }).filter(function (group) { return group.length > 1; });
    var duplicateRows = duplicateGroups.reduce(function (sum, group) { return sum + group.length; }, 0);
    var detailRows = duplicateGroups.slice(0, 25).map(function (group, index) {
      return [
        index + 1,
        group.map(function (item) { return item.rowNumber; }).join(", "),
        idColumn ? group.map(function (item) { return String(item.id || "Blank"); }).join(", ") : "N/A",
        group.length
      ];
    });
    if (duplicateGroups.length > 25) detailRows.push(["More groups", duplicateGroups.length - 25, idColumn ? "Hidden for compact output" : "N/A", ""]);
    return [
      { type: "paragraph", data: { text: "Found " + duplicateRows + " duplicate rows across " + duplicateGroups.length + " duplicate group(s). Row numbers are spreadsheet row numbers including the header row." } },
      { type: "table", data: { withHeadings: true, rows: [["Duplicate Group", "Row Numbers", idColumn ? columnLabel(idColumn) : "ID", "Rows in Group"]].concat(detailRows) } }
    ];
  }

  function correlationBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(correlation|correlate|pearson)\b/i.test(q)) return [];
    var pair = manualNumericPair(metadata, q);
    if (!pair && /\bbetween\b/i.test(q)) return [];
    var numeric = mentionedNumericColumns(metadata, q);
    if (!pair && numeric.length < 2) numeric = Object.keys(metadata.numericStats || {}).filter(function (name) { return promptMentionsColumn(q, name); });
    if (!pair && numeric.length >= 2) pair = { x: numeric[0], y: numeric[1] };
    if (!pair || !pair.x || !pair.y || pair.x === pair.y) return [];
    var x = pair.x, y = pair.y;
    var pairs = rows.map(function (row) { return [numericValue(rowValue(row, x)), numericValue(rowValue(row, y))]; }).filter(function (pair) { return pair[0] !== null && pair[1] !== null; });
    if (pairs.length < 2) return [];
    var meanX = pairs.reduce(function (sum, pair) { return sum + pair[0]; }, 0) / pairs.length;
    var meanY = pairs.reduce(function (sum, pair) { return sum + pair[1]; }, 0) / pairs.length;
    var num = 0, denX = 0, denY = 0;
    pairs.forEach(function (pair) {
      var dx = pair[0] - meanX;
      var dy = pair[1] - meanY;
      num += dx * dy;
      denX += dx * dx;
      denY += dy * dy;
    });
    var corr = denX && denY ? num / Math.sqrt(denX * denY) : 0;
    return [
      { type: "paragraph", data: { text: "Computed Pearson correlation between `" + x + "` and `" + y + "` from " + pairs.length + " comparable rows." } },
      { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Pearson correlation", roundNumber(corr), "COV(" + x + "," + y + ") / (STD(" + x + ") x STD(" + y + "))"], ["Comparable rows", pairs.length, "Rows where both values are numeric"]] } }
    ];
  }

  function iqrOutlierBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\bq3\s*\+\s*1\.5\s*[x×*]\s*iqr|iqr\b/i.test(q)) return [];
    var metric = manualMetricColumn(metadata, q);
    if (!metric) return [];
    var values = rows.map(function (row) { return numericValue(rowValue(row, metric)); }).filter(function (value) { return value !== null; }).sort(function (a, b) { return a - b; });
    if (!values.length) return [];
    function percentile(sorted, p) {
      var pos = (sorted.length - 1) * p;
      var base = Math.floor(pos);
      var rest = pos - base;
      return sorted[base] + ((sorted[base + 1] != null ? sorted[base + 1] : sorted[base]) - sorted[base]) * rest;
    }
    var q1 = percentile(values, 0.25);
    var q3 = percentile(values, 0.75);
    var iqr = q3 - q1;
    var upperThreshold = q3 + 1.5 * iqr;
    var lowerThreshold = q1 - 1.5 * iqr;
    var upperOnly = /\babove\s+q3|greater\s+than\s+q3|high(?:er)?\s+outliers?\b/i.test(q);
    var count = upperOnly
      ? values.filter(function (value) { return value > upperThreshold; }).length
      : values.filter(function (value) { return value < lowerThreshold || value > upperThreshold; }).length;
    var countLabel = upperOnly ? "Rows above upper threshold" : "Rows outside IQR fences";
    var countFormula = upperOnly ? "`" + metric + "` > upper threshold" : "`" + metric + "` < lower threshold OR `" + metric + "` > upper threshold";
    return [
      { type: "paragraph", data: { text: upperOnly ? "Computed high outliers for `" + metric + "` using Q3 + 1.5 x IQR." : "Computed IQR outliers for `" + metric + "` using lower and upper 1.5 x IQR fences." } },
      { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Q1", chartNumber(q1), "25th percentile"], ["Q3", chartNumber(q3), "75th percentile"], ["Lower threshold", chartNumber(lowerThreshold), "Q1 - 1.5 x IQR"], ["Upper threshold", chartNumber(upperThreshold), "Q3 + 1.5 x IQR"], [countLabel, count, countFormula], ["Numeric rows checked", values.length, "All numeric values"]] } }
    ];
  }

  function distinctAndRepeatBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(unique|distinct|more\s+than\s+one\s+encounter|more\s+than\s+once|appear\s+more\s+than\s+once|repeated?|repeat\s+patients?)\b/i.test(q)) return [];
    var idColumn = resolveLoosePromptColumn(metadata, q, "identifier") || findColumnByWords(metadata, ["patient id", "patientid", "customer id", "customerid", "id"]);
    if (!idColumn) return [];
    var counts = {};
    rows.forEach(function (row) {
      var value = String(rowValue(row, idColumn) || "").trim();
      if (!value) return;
      counts[value] = (counts[value] || 0) + 1;
    });
    var ids = Object.keys(counts);
    if (/\bmore\s+than\s+one\s+encounter|more\s+than\s+once|appear\s+more\s+than\s+once|repeated?|repeat\s+patients?\b/i.test(q)) {
      var repeat = ids.filter(function (id) { return counts[id] > 1; });
      return [
        { type: "paragraph", data: { text: "Computed repeat IDs from `" + idColumn + "` across " + rows.length + " parsed rows." } },
        { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["IDs with more than one encounter", repeat.length, "COUNT IDs where row count > 1"], ["Unique IDs checked", ids.length, "COUNT_DISTINCT `" + idColumn + "`"], ["Rows checked", rows.length, "All parsed rows"]] } }
      ];
    }
    return [
      { type: "paragraph", data: { text: "Computed distinct count from `" + idColumn + "` across " + rows.length + " parsed rows." } },
      { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Unique " + columnLabel(idColumn), ids.length, "COUNT_DISTINCT `" + idColumn + "`"], ["Rows checked", rows.length, "All parsed rows"]] } }
    ];
  }

  function claimStatusProviderBucketBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(insurer|insurance\s+provider|provider|payer)\b/i.test(q) || !/\bapproved|pending|rejected|claim\b/i.test(q)) return [];
    if (!/\b(claim\s*status|approved\s*\/\s*pending|approved\s*,\s*pending|pending|rejected|status\s+buckets?|bucket|buckets|stored)\b/i.test(q)) return [];
    var provider = findColumnByWords(metadata, ["insurance provider", "insuranceprovider", "payer", "insurer", "provider"]);
    var status = findColumnByWords(metadata, ["claim status", "claimstatus", "status"]);
    var amount = findColumnByWords(metadata, ["insurance claim", "insuranceclaim", "insuranceclaim_inr", "claim amount", "approved claim amount"]);
    if (!provider || !status || !amount) return [];
    var buckets = {};
    rows.forEach(function (row) {
      var providerName = String(rowValue(row, provider) || "Blank").trim() || "Blank";
      var statusName = String(rowValue(row, status) || "Blank").trim() || "Blank";
      if (!buckets[providerName]) buckets[providerName] = { provider: providerName, approved: 0, pending: 0, rejected: 0, other: 0, total: 0, rows: 0 };
      var value = rowNumber(row, amount);
      var key = /^approved$/i.test(statusName) ? "approved" : /^pending$/i.test(statusName) ? "pending" : /^rejected$/i.test(statusName) ? "rejected" : "other";
      buckets[providerName][key] += value;
      buckets[providerName].total += value;
      buckets[providerName].rows += 1;
    });
    var order = Object.keys(buckets).sort(function (a, b) { return buckets[b].total - buckets[a].total; });
    if (!order.length) return [];
    var top = buckets[order[0]];
    var tableRows = [["Provider", "Approved", "Pending", "Rejected", "Other", "Total", "Rows"]].concat(order.map(function (key) {
      var item = buckets[key];
      return [item.provider, chartNumber(item.approved), chartNumber(item.pending), chartNumber(item.rejected), chartNumber(item.other), chartNumber(item.total), item.rows];
    }));
    return [
      { type: "paragraph", data: { text: "Highest insurer/provider by stored approved/pending/rejected claim amount is `" + top.provider + "` with total " + chartNumber(top.total) + ". Formula: group by `" + provider + "`, then SUM `" + amount + "` within `" + status + "` buckets." } },
      { type: "table", data: { withHeadings: true, rows: tableRows } }
    ];
  }

  function formulaValidationBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "")
      .replace(/(Total\s*Bill|TotalBill|PatientPayment|Patient\s*Payment|InsuranceClaim|Insurance\s*Claim)\s*\?\s*(Total\s*Bill|TotalBill|PatientPayment|Patient\s*Payment|InsuranceClaim|Insurance\s*Claim)/ig, "$1 - $2")
      .replace(/>\s*\?\s*([0-9])/g, "> $1");
    if (!metadata || !rows.length || !/\b(verify|validate|check)\b/i.test(q) || !/\bmismatch/i.test(q)) return [];
    if (/\brevenue\b/i.test(q) && /units?\s*sold|unitssold|quantity/i.test(q) && /unit\s*price|unitprice|price/i.test(q) && /discount/i.test(q)) {
      var revenueColumn = resolveLoosePromptColumn(metadata, "Revenue", "number");
      var unitsColumn = resolveLoosePromptColumn(metadata, "UnitsSold", "number") || resolveLoosePromptColumn(metadata, "Quantity", "number");
      var priceColumn = resolveLoosePromptColumn(metadata, "UnitPrice", "number") || resolveLoosePromptColumn(metadata, "Price", "number");
      var discountColumnName = resolveLoosePromptColumn(metadata, "Discount", "number");
      var revenueToleranceMatch = q.match(/\b(?:mismatches?|difference|diff)\s*>\s*(?:rs\.?|inr|[\u20b9$])?\s*([0-9]+(?:\.[0-9]+)?)/i);
      var revenueTolerance = revenueToleranceMatch ? Number(revenueToleranceMatch[1]) : 0.02;
      if (revenueColumn && unitsColumn && priceColumn && discountColumnName) {
        var revenueMismatchCount = 0;
        var revenueCheckedRows = 0;
        rows.forEach(function (row) {
          var actual = rowNumber(row, revenueColumn);
          var discount = rowNumber(row, discountColumnName);
          var expected = rowNumber(row, unitsColumn) * rowNumber(row, priceColumn) * (1 - (discount / 100));
          revenueCheckedRows += 1;
          if (Math.abs(actual - expected) > revenueTolerance) revenueMismatchCount += 1;
        });
        var revenueFormula = "`" + revenueColumn + "` = `" + unitsColumn + "` x `" + priceColumn + "` x (1 - `" + discountColumnName + "` / 100)";
        return [
          { type: "paragraph", data: { text: "Exact formula validation from " + rows.length + " parsed workbook rows. Formula: " + revenueFormula + "; mismatch threshold > " + revenueTolerance + "." } },
          { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Mismatches", revenueMismatchCount, "ABS(actual - expected) > " + revenueTolerance], ["Matches", Math.max(0, revenueCheckedRows - revenueMismatchCount), "Rows checked - mismatches"], ["Rows checked", revenueCheckedRows, "All parsed rows"]] } }
        ];
      }
    }
    var match = q.match(/\b(?:verify|validate|check)\s+(.+?)\s*=\s*(.+?)(?:\.|\?|$)/i);
    if (!match) return [];
    var leftColumn = resolveLoosePromptColumn(metadata, match[1], "number");
    var rhs = String(match[2] || "");
    var multiplyMatch = rhs.match(/(.+?)\s*(?:x|×|\*)\s*([+-]?[0-9]+(?:\.[0-9]+)?)/i);
    if (leftColumn && multiplyMatch) {
      var multiplyColumn = resolveLoosePromptColumn(metadata, multiplyMatch[1], "number");
      var multiplier = Number(multiplyMatch[2]);
      if (multiplyColumn && Number.isFinite(multiplier)) {
        var multiplyToleranceMatch = q.match(/\b(?:tolerance|mismatches?|difference|diff)\s*(?:>|of|is|=|:)?\s*(?:rs\.?|inr|₹|\$)?\s*([0-9]+(?:\.[0-9]+)?)/i);
        var multiplyTolerance = multiplyToleranceMatch ? Number(multiplyToleranceMatch[1]) : 0.02;
        var multiplyCheckedRows = 0;
        var multiplyMismatchCount = 0;
        rows.forEach(function (row) {
          var actual = rowNumber(row, leftColumn);
          var expected = rowNumber(row, multiplyColumn) * multiplier;
          multiplyCheckedRows += 1;
          if (Math.abs(actual - expected) > multiplyTolerance) multiplyMismatchCount += 1;
        });
        var multiplyFormula = "`" + leftColumn + "` = `" + multiplyColumn + "` x " + multiplier;
        return [
          { type: "paragraph", data: { text: "Exact formula validation from " + rows.length + " parsed workbook rows. Formula: " + multiplyFormula + "; mismatch threshold > " + multiplyTolerance + "." } },
          { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Mismatches", multiplyMismatchCount, "ABS(actual - expected) > " + multiplyTolerance], ["Matches", Math.max(0, multiplyCheckedRows - multiplyMismatchCount), "Rows checked - mismatches"], ["Rows checked", multiplyCheckedRows, "All parsed rows"]] } }
        ];
      }
    }
    var operator = rhs.indexOf("+") >= 0 ? "+" : (/[−–-]/.test(rhs) ? "-" : "");
    if (!leftColumn || !operator) return [];
    var parts = rhs.split(operator === "+" ? /\+/ : /[−–-]/).map(function (part) {
      return resolveLoosePromptColumn(metadata, part, "number");
    }).filter(Boolean);
    if (parts.length < 2) return [];
    var toleranceMatch = q.match(/\b(?:mismatches?|difference|diff)\s*>\s*(?:rs\.?|inr|₹|\$)?\s*([0-9]+(?:\.[0-9]+)?)/i);
    var tolerance = toleranceMatch ? Number(toleranceMatch[1]) : 0.02;
    var checkedRows = 0;
    var mismatchCount = 0;
    rows.forEach(function (row) {
      var actual = rowNumber(row, leftColumn);
      var expected = operator === "+"
        ? parts.reduce(function (sum, column) { return sum + rowNumber(row, column); }, 0)
        : parts.slice(1).reduce(function (value, column) { return value - rowNumber(row, column); }, rowNumber(row, parts[0]));
      checkedRows += 1;
      if (Math.abs(actual - expected) > tolerance) mismatchCount += 1;
    });
    var formula = "`" + leftColumn + "` = " + parts.map(function (column) { return "`" + column + "`"; }).join(" " + operator + " ");
    return [
      { type: "paragraph", data: { text: "Exact formula validation from " + rows.length + " parsed workbook rows. Formula: " + formula + "; mismatch threshold > " + tolerance + "." } },
      { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Mismatches", mismatchCount, "ABS(" + formula + ") > " + tolerance], ["Matches", Math.max(0, checkedRows - mismatchCount), "Rows checked - mismatches"], ["Rows checked", checkedRows, "All parsed rows"]] } }
    ];
  }

  function monthHighLowMetricBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\bmonth\b/i.test(q) || !/\bhighest|lowest|max|min\b/i.test(q)) return [];
    var dateColumn = (metadata.dateColumns || [])[0] || findColumnByWords(metadata, ["date", "admission date", "admissiondate", "created_at"]);
    var metric = manualMetricColumn(metadata, q);
    if (!dateColumn || !metric || !(metadata.numericStats || {})[metric]) return [];
    var months = {};
    rows.forEach(function (row) {
      var time = parsedSheetTime(rowValue(row, dateColumn));
      if (time === null) return;
      var date = new Date(time);
      var key = date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, "0");
      months[key] = (months[key] || 0) + rowNumber(row, metric);
    });
    var order = Object.keys(months).sort(function (a, b) { return months[b] - months[a]; });
    if (!order.length) return [];
    var high = order[0];
    var low = order[order.length - 1];
    return [
      { type: "paragraph", data: { text: "Computed monthly `" + metric + "` totals from `" + dateColumn + "` across " + rows.length + " parsed rows." } },
      { type: "table", data: { withHeadings: true, rows: [["Metric", "Month", "Value", "Formula"], ["Highest monthly " + columnLabel(metric), high, chartNumber(months[high]), "GROUP BY month, SUM `" + metric + "`"], ["Lowest monthly " + columnLabel(metric), low, chartNumber(months[low]), "GROUP BY month, SUM `" + metric + "`"]] } }
    ];
  }

  function thresholdCountBlocksV2(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "").replace(/\?\s*([0-9])/g, "$1");
    if (!metadata || !rows.length || !thresholdCountQuestion(q)) return [];
    var missingMetric = missingRequestedMetric(metadata, q);
    if (missingMetric) return missingColumnGuardBlocks(metadata, q, { metric: missingMetric });
    var metric = manualMetricColumn(metadata, q);
    if (!metric || !(metadata.numericStats || {})[metric]) return [];
    var negative = /\bnegative\b/i.test(q);
    var lessEqual = /\b(less\s+than\s+or\s+equal\s+to|at\s+most|no\s+more\s+than)\b/i.test(q);
    var greaterEqual = /\b(greater\s+than\s+or\s+equal\s+to|at\s+least)\b/i.test(q);
    var lessThan = /\b(below|under|less\s+than|fewer\s+than)\b/i.test(q) && !lessEqual;
    var symbolMatch = negative ? null : q.match(/(>=|<=|>|<)\s*(?:rs\.?|inr|[\u20b9$])?\s*([+-]?[0-9][0-9,]*(?:\.[0-9]+)?)/i);
    var op = symbolMatch ? symbolMatch[1] : (negative || lessThan || lessEqual ? (lessEqual ? "<=" : "<") : (greaterEqual ? ">=" : ">"));
    var thresholdMatch = negative ? null : (symbolMatch ? [symbolMatch[0], symbolMatch[2]] : q.match(/\b(?:above|over|greater\s+than\s+or\s+equal\s+to|greater\s+than|more\s+than|at\s+least|below|under|less\s+than\s+or\s+equal\s+to|less\s+than|fewer\s+than|at\s+most|no\s+more\s+than)\s*(?:rs\.?|inr|[\u20b9$])?\s*([+-]?[0-9][0-9,]*(?:\.[0-9]+)?)/i));
    var threshold = negative ? 0 : (thresholdMatch ? Number(String(thresholdMatch[1]).replace(/,/g, "")) : null);
    if (threshold === null || !Number.isFinite(threshold)) return [];
    var count = 0;
    var numericRows = 0;
    rows.forEach(function (row) {
      var value = numericValue(rowValue(row, metric));
      if (value === null) return;
      numericRows += 1;
      if (op === ">" ? value > threshold : (op === ">=" ? value >= threshold : (op === "<=" ? value <= threshold : value < threshold))) count += 1;
    });
    var metricLabel = columnLabel(metric);
    var condition = "`" + metric + "` " + op + " " + threshold;
    return [
      { type: "paragraph", data: { text: "Exact threshold count from " + rows.length + " parsed workbook rows. Formula: COUNT rows where " + condition + "." } },
      { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Rows matching threshold", count, "COUNT rows where " + condition], ["Metric column", metricLabel, "`" + metric + "`"], ["Numeric rows checked", numericRows, "Rows with numeric `" + metric + "`"], ["Rows checked", rows.length, "All parsed rows"]] } }
    ];
  }

  function thresholdCountBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !/\b(count|how\s+many)\b/i.test(q) || !/\b(above|over|greater\s+than|below|under|less\s+than|negative)\b/i.test(q)) return [];
    var metric = manualMetricColumn(metadata, q);
    if (!metric || !(metadata.numericStats || {})[metric]) return [];
    var negative = /\bnegative\b/i.test(q);
    var op = negative || /\b(below|under|less\s+than)\b/i.test(q) ? "<" : ">";
    var match = negative ? null : q.match(/\b(?:above|over|greater\s+than|below|under|less\s+than)\s*(?:rs\.?|inr|₹|\$)?\s*([+-]?[0-9]+(?:\.[0-9]+)?)/i);
    var threshold = negative ? 0 : (match ? Number(match[1]) : null);
    if (threshold === null || !Number.isFinite(threshold)) return [];
    var count = 0;
    var numericRows = 0;
    rows.forEach(function (row) {
      var value = numericValue(rowValue(row, metric));
      if (value === null) return;
      numericRows += 1;
      if (op === ">" ? value > threshold : value < threshold) count += 1;
    });
    return [
      { type: "paragraph", data: { text: "Exact threshold count from " + rows.length + " parsed workbook rows. Formula: `" + metric + "` " + op + " " + threshold + "." } },
      { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Rows matching threshold", count, "`" + metric + "` " + op + " " + threshold], ["Numeric rows checked", numericRows, "Rows with numeric `" + metric + "`"], ["Rows checked", rows.length, "All parsed rows"]] } }
    ];
  }

  function filteredAggregationBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !filteredAggregationQuestion(q)) return [];
    var metric = manualMetricColumn(metadata, questionWithoutManualFilterValues(metadata, q)) || manualMetricColumn(metadata, q);
    var filters = manualFilters(metadata, q);
    if (!filters.length) {
      var single = manualFilter(metadata, q);
      if (single) filters = [single];
    }
    var wantsCount = /\b(count|how\s+many|number\s+of)\b/i.test(q) && !/\b(total|sum|average|avg|mean|median|min(?:imum)?|max(?:imum)?)\b/i.test(q);
    if ((!metric && !wantsCount) || !filters.length || (metric && !(metadata.numericStats || {})[metric])) return [];
    var filtered = rows.filter(function (row) {
      return filters.every(function (filter) {
        return columnKey(rowValue(row, filter.column)) === columnKey(filter.value);
      });
    });
    var filterText = filters.map(function (filter) { return "`" + filter.column + "` = `" + filter.value + "`"; }).join(" AND ");
    var groupColumns = promptGroupColumns(metadata, q, "").filter(function (column) {
      return column && !filters.some(function (filter) { return filter.column === column; }) && !isForbiddenColumn(metadata, q, column);
    });
    if (groupColumns.length) {
      var grouped = groupedByColumns(filtered, groupColumns);
      var mode = wantsCount ? "count" : aggregationMode(q);
      var valueMetric = wantsCount ? "__row_count" : metric;
      var order = grouped.order.slice();
      var ascending = /\b(lowest\s+to\s+highest|ascending|asc|low\s+to\s+high)\b/i.test(q);
      order.sort(function (a, b) {
        var av = aggregateRows(grouped.grouped[a].rows, valueMetric, mode);
        var bv = aggregateRows(grouped.grouped[b].rows, valueMetric, mode);
        return (ascending ? av - bv : bv - av) || a.localeCompare(b);
      });
      var valueHeader = wantsCount ? "Count" : columnLabel(metric) + " (" + mode + ")";
      var tableRows = [groupColumns.map(columnLabel).concat([valueHeader, "Rows", "Filtered Percent"])].concat(order.map(function (key) {
        var item = grouped.grouped[key];
        var value = aggregateRows(item.rows, valueMetric, mode);
        return item.parts.concat([String(mode === "average" ? tolerantDisplayValue(value) : chartNumber(value)), String(item.rows.length), filtered.length ? formatPercent(item.rows.length * 100 / filtered.length) : "0%"]);
      }));
      var chartLimit = requestedLimit(q, 20);
      var chartOrder = order.slice(0, chartLimit);
      var chartType = manualChartType(q, "bar");
      return [
        { type: "paragraph", data: { text: "Exact filtered grouped " + (wantsCount ? "count" : mode) + " from " + rows.length + " parsed workbook rows. Filters: " + filterText + ". Formula: apply filters, group by `" + groupColumns.join("`, `") + "`, then " + (wantsCount ? "COUNT rows" : mode.toUpperCase() + " `" + metric + "`") + "." } },
        { type: "table", data: { withHeadings: true, rows: tableRows } },
        {
          type: "chart",
          data: {
            type: chartType,
            title: valueHeader + " by " + groupColumns.map(columnLabel).join(" and "),
            labels: manualChartLabelsForType(chartType, chartOrder.map(function (key) { return grouped.grouped[key].parts.join(" / "); })).join(","),
            values: chartOrder.map(function (key) { return chartNumber(aggregateRows(grouped.grouped[key].rows, valueMetric, mode)); }).join(","),
            caption: "Generated from " + filtered.length + " filtered rows out of " + rows.length + ". Formula: " + filterText + "; group by `" + groupColumns.join("`, `") + "`.",
            color: "#2563eb",
            colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
          }
        }
      ];
    }
    if (wantsCount) {
      return [
        { type: "paragraph", data: { text: "Exact filtered count from " + rows.length + " parsed workbook rows. Formula: COUNT rows where " + filterText + "." } },
        { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Matching rows", filtered.length, "COUNT rows where " + filterText], ["Rows checked", rows.length, "All parsed rows"]] } }
      ];
    }
    var values = filtered.map(function (row) { return numericValue(rowValue(row, metric)); }).filter(function (value) { return value !== null; });
    if (!values.length && filtered.length) values = [0];
    var sum = values.reduce(function (total, value) { return total + value; }, 0);
    var mode = aggregationMode(q);
    var value = mode === "average" ? (sum / values.length) : (mode === "median" ? medianNumber(values) : (mode === "min" ? Math.min.apply(Math, values) : (mode === "max" ? Math.max.apply(Math, values) : sum)));
    var op = mode === "average" ? "average" : (mode === "median" ? "median" : (mode === "min" ? "minimum" : (mode === "max" ? "maximum" : "total")));
    var formulaOp = mode === "average" ? "AVG" : (mode === "median" ? "MEDIAN" : (mode === "min" ? "MIN" : (mode === "max" ? "MAX" : "SUM")));
    var formula = formulaOp + " `" + metric + "` where " + filterText;
    return [
      { type: "paragraph", data: { text: "Exact filtered " + op + " from " + rows.length + " parsed workbook rows. Formula: " + formula + "." } },
      { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], [op.charAt(0).toUpperCase() + op.slice(1) + " " + columnLabel(metric), mode === "average" ? tolerantDisplayValue(value) : chartNumber(value), formula], ["Matching rows", filtered.length, filterText], ["Rows checked", rows.length, "All parsed rows"]] } }
    ];
  }

  function filteredRowBlocks(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    if (!metadata || !rows.length || !filteredRowListingQuestion(q)) return [];
    var filters = manualFilters(metadata, q);
    if (!filters.length) {
      var single = manualFilter(metadata, q);
      if (single) filters = [single];
    }
    if (!filters.length) return [];
    var filtered = rows.filter(function (row) {
      return filters.every(function (filter) {
        return columnKey(rowValue(row, filter.column)) === columnKey(filter.value);
      });
    });
    var filterText = filters.map(function (filter) { return "`" + filter.column + "` = `" + filter.value + "`"; }).join(" AND ");
    var headings = (metadata.headings || []).slice(0, 16);
    if (!headings.length && filtered.length) headings = Object.keys(filtered[0]).slice(0, 16);
    var limit = Math.min(requestedLimit(q, 50) || 50, 100);
    var blocks = [
      { type: "paragraph", data: { text: "Exact filtered row lookup from " + rows.length + " parsed workbook rows. Formula: return rows where " + filterText + "." } },
      { type: "table", data: { withHeadings: true, rows: [["Metric", "Value", "Formula"], ["Matching rows", filtered.length, "COUNT rows where " + filterText], ["Rows checked", rows.length, "All parsed rows"]] } }
    ];
    if (!filtered.length) return blocks;
    var previewRows = [headings.map(columnLabel)].concat(filtered.slice(0, limit).map(function (row) {
      return headings.map(function (heading) {
        var value = rowValue(row, heading);
        return value == null ? "" : String(value);
      });
    }));
    if (filtered.length > limit) {
      var note = headings.map(function () { return ""; });
      note[0] = "Showing first " + limit + " of " + filtered.length + " matching rows";
      previewRows.push(note);
    }
    blocks.push({ type: "table", data: { withHeadings: true, rows: previewRows } });
    return blocks;
  }

  function manualDeterministicBlocks(metadata, question) {
    var duplicateBlocks = duplicateDetailBlocks(metadata, question);
    if (duplicateBlocks.length) return duplicateBlocks;
    var distinctBlocks = distinctAndRepeatBlocks(metadata, question);
    if (distinctBlocks.length) return distinctBlocks;
    var correlationResultBlocks = correlationBlocks(metadata, question);
    if (correlationResultBlocks.length) return correlationResultBlocks;
    var iqrBlocks = iqrOutlierBlocks(metadata, question);
    if (iqrBlocks.length) return iqrBlocks;
    var formulaBlocks = formulaValidationBlocks(metadata, question);
    if (formulaBlocks.length) return formulaBlocks;
    var claimStatusBlocks = claimStatusProviderBucketBlocks(metadata, question);
    if (claimStatusBlocks.length) return claimStatusBlocks;
    var monthHighLow = monthHighLowMetricBlocks(metadata, question);
    if (monthHighLow.length) return monthHighLow;
    var thresholdBlocks = thresholdCountBlocksV2(metadata, question);
    if (thresholdBlocks.length) return thresholdBlocks;
    thresholdBlocks = thresholdCountBlocks(metadata, question);
    if (thresholdBlocks.length) return thresholdBlocks;
    var filteredBlocks = filteredAggregationBlocks(metadata, question);
    if (filteredBlocks.length) return filteredBlocks;
    var filteredRowResultBlocks = filteredRowBlocks(metadata, question);
    if (filteredRowResultBlocks.length) return filteredRowResultBlocks;
    var relationshipBlocks = manualRelationshipBlocks(metadata, question);
    if (relationshipBlocks.length) return relationshipBlocks;
    var scalarBlocks = scalarManualCheckBlocks(metadata, question);
    if (scalarBlocks.length) return scalarBlocks;
    var totalGroupBlocks = totalMetricByGroupBlocks(metadata, question);
    if (totalGroupBlocks.length) return totalGroupBlocks;
    var groupedCountResultBlocks = groupedCountBlocks(metadata, question);
    if (groupedCountResultBlocks.length) return groupedCountResultBlocks;
    var groupedBlocks = groupedMetricBlocks(metadata, question);
    if (groupedBlocks.length) return groupedBlocks;
    var scalarMetricBlocks = scalarMetricExecutionBlocks(metadata, question);
    if (scalarMetricBlocks.length) return scalarMetricBlocks;
    var kpiBlocks = kpiDashboardBlocks(metadata, question);
    if (kpiBlocks.length) return kpiBlocks;
    var pivotBlocks = pivotMatrixBlocks(metadata, question);
    if (pivotBlocks.length) return pivotBlocks;
    var outlierBlocks = outlierRowBlocks(metadata, question);
    if (outlierBlocks.length) return outlierBlocks;
    var earlyYearTrendBlocks = (/\b(year|yearly|annual|year-wise|year\s+wise)\b/i.test(String(question || "")) || columnKey(question).indexOf("yearwise") >= 0 || columnKey(question).indexOf("byyear") >= 0) ? timeTrendMetricBlocks(metadata, question) : [];
    if (earlyYearTrendBlocks.length) return earlyYearTrendBlocks;
    var bucketBlocks = genericBucketMonthlyBlocks(metadata, question);
    if (bucketBlocks.length) return bucketBlocks;
    var timeSeriesGroupBlocks = genericTimeSeriesByGroupBlocks(metadata, question);
    if (timeSeriesGroupBlocks.length) return timeSeriesGroupBlocks;
    var periodConditionBlocks = genericPeriodConditionBlocks(metadata, question);
    if (periodConditionBlocks.length) return periodConditionBlocks;
    var contributionShareBlocks = genericContributionShareBlocks(metadata, question);
    if (contributionShareBlocks.length) return contributionShareBlocks;
    var rankMismatchBlocks = genericRankMismatchBlocks(metadata, question);
    if (rankMismatchBlocks.length) return rankMismatchBlocks;
    var genericCombination = genericCombinationBlocks(metadata, question);
    if (genericCombination.length) return genericCombination;
    var genericOutliers = genericOutlierBlocks(metadata, question);
    if (genericOutliers.length) return genericOutliers;
    var perUnitBlocks = genericPerUnitBlocks(metadata, question);
    if (perUnitBlocks.length) return perUnitBlocks;
    var exactBlocks = exactCountBlocks(metadata, question);
    if (exactBlocks.length) return exactBlocks;
    var multiMetricBlocks = genericMultiMetricGroupBlocks(metadata, question);
    if (multiMetricBlocks.length) return multiMetricBlocks;
    var riskyBlocks = riskyProductsBlocks(metadata, question);
    if (riskyBlocks.length) return riskyBlocks;
    var momBlocks = monthOverMonthConditionBlocks(metadata, question);
    if (momBlocks.length) return momBlocks;
    var codBlocks = codVsNonCodMonthlyBlocks(metadata, question);
    if (codBlocks.length) return codBlocks;
    var aovBlocks = aovByMonthCategoryBlocks(metadata, question);
    if (aovBlocks.length) return aovBlocks;
    var waterfallBlocks = waterfallRevenueNetProfitBlocks(metadata, question);
    if (waterfallBlocks.length) return waterfallBlocks;
    var paymentShareBlocks = paymentShareComparisonBlocks(metadata, question);
    if (paymentShareBlocks.length) return paymentShareBlocks;
    var contributionBlocks = categoryContributionComparisonBlocks(metadata, question);
    if (contributionBlocks.length) return contributionBlocks;
    var monthlyFormulaBlocks = monthlyFormulaComparisonBlocks(metadata, question);
    if (monthlyFormulaBlocks.length) return monthlyFormulaBlocks;
    var cityMarginBlocks = cityProfitMarginBlocks(metadata, question);
    if (cityMarginBlocks.length) return cityMarginBlocks;
    var productMismatchBlocks = productRevenueMarginMismatchBlocks(metadata, question);
    if (productMismatchBlocks.length) return productMismatchBlocks;
    var discountImpactBlocks = discountImpactComparisonBlocks(metadata, question);
    if (discountImpactBlocks.length) return discountImpactBlocks;
    var customerSegmentBlocks = customerSegmentMonthlyFallbackBlocks(metadata, question);
    if (customerSegmentBlocks.length) return customerSegmentBlocks;
    var combinationBlocks = combinationRankingBlocks(metadata, question);
    if (combinationBlocks.length) return combinationBlocks;
    var monthlySegmentBlocks = monthlySegmentPerformanceBlocks(metadata, question);
    if (monthlySegmentBlocks.length) return monthlySegmentBlocks;
    var ratioBlocks = formulaRatioBlocks(metadata, question);
    if (ratioBlocks.length) return ratioBlocks;
    var trendBlocks = timeTrendMetricBlocks(metadata, question);
    if (trendBlocks.length) return trendBlocks;
    var aggregateBlocks = aggregateRankingBlocks(metadata, question);
    if (aggregateBlocks.length) return aggregateBlocks;
    var sortedBlocks = sortedGroupBlocks(metadata, question);
    if (sortedBlocks.length) return sortedBlocks;
    var monthlyBlocks = monthlyDistributionBlocks(metadata, question);
    if (monthlyBlocks.length) return monthlyBlocks;
    var derivedBlocks = derivedMetricBlocks(metadata, question);
    if (derivedBlocks.length) return derivedBlocks;
    return groupedMetricBlocks(metadata, question);
  }

  function manualMetricColumn(metadata, question) {
    var q = columnKey(question);
    var numeric = Object.keys(metadata.numericStats || {});
    var text = String(question || "");
    if (/\brevenue\b/i.test(text) && !strictRevenueColumn(metadata)) return "";
    if (/\bprofit\b/i.test(text) && !profitColumn(metadata)) return "";
    var exactMetric =
      /\btreatment\s+cost\b/i.test(text) ? findColumnByWords(metadata, ["treatment cost", "treatmentcost", "treatmentcost_inr"]) :
      /\bmedication\s+cost\b/i.test(text) ? findColumnByWords(metadata, ["medication cost", "medicationcost", "medicationcost_inr"]) :
      /\blab\s+cost\b/i.test(text) ? findColumnByWords(metadata, ["lab cost", "labcost", "labcost_inr"]) :
      /\binsurance\s+(claim|coverage)\b/i.test(text) ? findColumnByWords(metadata, ["insurance claim", "insuranceclaim", "insuranceclaim_inr", "insurance coverage", "insurancecoveragepct"]) :
      /\b(patient\s+payment|out\s*of\s*pocket|oop)\b/i.test(text) ? findColumnByWords(metadata, ["patient payment", "patientpayment", "patientpayment_inr"]) :
      /\b(wait\s*time)\b/i.test(text) ? findColumnByWords(metadata, ["wait time", "waittimeminutes", "waittime"]) :
      /\b(length\s+of\s+stay|los)\b/i.test(text) ? findColumnByWords(metadata, ["length of stay", "lengthofstaydays", "los"]) :
      /\bsatisfaction\b/i.test(text) ? findColumnByWords(metadata, ["satisfaction score", "satisfactionscore", "satisfaction"]) :
      /\bage\b/i.test(text) ? findColumnByWords(metadata, ["age", "patient age"]) :
      /\binventory\s+after\b/i.test(text) ? findColumnByWords(metadata, ["inventory after", "inventoryafter", "ending inventory", "inventory end"]) :
      /\binventory\s+before\b/i.test(text) ? findColumnByWords(metadata, ["inventory before", "inventorybefore", "starting inventory", "inventory start"]) :
      /\bsupplier\s+lead\s*time\b/i.test(text) ? findColumnByWords(metadata, ["supplier lead time", "supplierleadtime", "supplierleadtimedays", "lead time"]) :
      /\bcustomer\s+rating\b/i.test(text) ? findColumnByWords(metadata, ["customer rating", "customerrating", "rating"]) :
      /\btemperature\s+deviation\b/i.test(text) ? findColumnByWords(metadata, ["temperature deviation", "temperaturedeviation", "temperaturedeviationc"]) :
      /\breturn\s+quantity\b/i.test(text) ? findColumnByWords(metadata, ["return quantity", "returnquantity", "returns"]) :
      /(?:\btotal\s*_?\s*comp(?:ensation)?\b|totalcomp|compensation)/i.test(text) ? findColumnByWords(metadata, ["total comp", "totalcomp", "totalcomp_inr", "total compensation", "compensation"]) :
      /(?:\bbonus\b|bonus_)/i.test(text) ? findColumnByWords(metadata, ["bonus", "bonus_inr"]) :
      /(?:\bsalary\b|salary_)/i.test(text) ? findColumnByWords(metadata, ["salary", "salary_inr"]) :
      /\b(bill|billing|hospital billing|total bill|totalbill|invoice amount)\b/i.test(text) ? revenueColumn(metadata) :
      "";
    if (exactMetric && numeric.indexOf(exactMetric) >= 0) return exactMetric;
    var directMetrics = promptMentionedColumns(metadata, text).filter(function (column) {
      return numeric.indexOf(column) >= 0;
    });
    if (asksForTimeBreakdown(text)) {
      var nonCalendarMetrics = directMetrics.filter(function (column) {
        return !/^(year|month|quarter|week)$/i.test(String(column || ""));
      });
      if (nonCalendarMetrics.length) directMetrics = nonCalendarMetrics;
    }
    var directMetric = directMetrics[0];
    if (directMetric) return directMetric;
    var preferred = [
      ["billing", ["totalbill", "bill", "billing", "amount"]],
      ["cost", ["cost", "expense"]],
      ["profit", ["profit", "netprofit", "margin"]],
      ["revenue", ["revenue", "sales", "amount", "total"]],
      ["quantity", ["quantity", "qty", "units"]],
      ["inventory", ["inventoryafter", "inventorybefore", "inventory", "stock"]],
      ["discount", ["discount"]],
      ["price", ["price", "unitprice"]]
    ];
    for (var p = 0; p < preferred.length; p += 1) {
      if (preferred[p][1].some(function (word) { return q.indexOf(word) >= 0; })) {
        var match = numeric.filter(function (name) {
          var key = columnKey(name);
          return preferred[p][1].some(function (word) { return key.indexOf(word) >= 0; });
        })[0];
        if (match) return match;
      }
    }
    return "";
  }

  function manualFilter(metadata, question) {
    var rows = rowsForAnalytics(metadata);
    var q = columnKey(question);
    var headings = metadata.headings || [];
    var explicitFilters = manualFilters(metadata, question);
    if (explicitFilters.length) return explicitFilters[0];
    for (var h = 0; h < headings.length; h += 1) {
      var heading = headings[h];
      var values = {};
      rows.slice(0, 500).forEach(function (row) {
        var value = String(rowValue(row, heading) || "").trim();
        if (value) values[value] = true;
      });
      var found = Object.keys(values).filter(function (value) {
        var key = columnKey(value);
        return key && q.indexOf(key) >= 0;
      })[0];
      if (found) {
        return { column: heading, value: found };
      }
    }
    return null;
  }

  function manualFilters(metadata, question) {
    metadata = metadata || {};
    var rows = rowsForAnalytics(metadata);
    var q = String(question || "");
    var filters = [];
    function addFilter(column, rawValue) {
      column = String(column || "").replace(/^(?:for|where|and|or|only|only\s+where|only\s+for)\s+/i, "").trim();
      rawValue = String(rawValue || "").replace(/[`'"]/g, "").trim();
      if (!column || rawValue === "") return;
      var resolvedColumn = resolveLoosePromptColumn(metadata, column, "category");
      if (!resolvedColumn) return;
      var values = {};
      rows.slice(0, 2000).forEach(function (row) {
        var value = String(rowValue(row, resolvedColumn) || "").trim();
        if (value) values[columnKey(value)] = value;
      });
      var resolvedValue = values[columnKey(rawValue)] || rawValue;
      if (filters.some(function (filter) { return filter.column === resolvedColumn && columnKey(filter.value) === columnKey(resolvedValue); })) return;
      filters.push({ column: resolvedColumn, value: resolvedValue });
    }
    (metadata.headings || []).forEach(function (heading) {
      var headingPattern = String(heading || "").replace(/[_-]+/g, " ").trim().split(/\s+/).filter(Boolean).map(escapeRegExp).join("[\\s_-]*");
      if (!headingPattern) return;
      var re = new RegExp("\\b" + headingPattern + "\\s*=\\s*([^,.;?]+?)(?=\\s*(?:,|\\s+(?:AND|OR)\\s+|[.;?]|$))", "ig");
      q.replace(re, function (_, value) {
        addFilter(heading, value);
        return "";
      });
    });
    if (filters.length) return filters;
    q.replace(/\b([A-Za-z][A-Za-z0-9_ ]{1,60}?)\s*=\s*([^,.;?]+?)(?=\s*(?:,|\s+(?:AND|OR)\s+|[.;?]|$))/ig, function (_, column, value) {
      addFilter(column, value);
      return "";
    });
    if (filters.length) return filters;
    var headings = metadata.headings || [];
    var questionKey = columnKey(q);
    for (var h = 0; h < headings.length; h += 1) {
      var heading = headings[h];
      var found = "";
      rows.slice(0, 800).forEach(function (row) {
        if (found) return;
        var value = String(rowValue(row, heading) || "").trim();
        if (value && questionKey.indexOf(columnKey(value)) >= 0) found = value;
      });
      if (found) addFilter(heading, found);
    }
    return filters;
  }

  function monthLabel(value) {
    var date = new Date(value);
    if (Number.isNaN(date.getTime())) return "";
    return date.toLocaleString("en", { month: "short", year: "numeric" });
  }

  function manualGraphBlocks(metadata, question) {
    if (!asksForGraph(question) || !metadata) return [];
    var rows = rowsForAnalytics(metadata);
    if (!rows.length) return [];
    var filter = manualFilter(metadata, question);
    var filtered = filter ? rows.filter(function (row) {
      return columnKey(rowValue(row, filter.column)) === columnKey(filter.value);
    }) : rows.slice();
    if (!filtered.length) filtered = rows.slice();
    var q = String(question || "");
    var dateColumn = (metadata.dateColumns || [])[0] || resolveColumn(metadata, "", "category");
    var metric = manualMetricColumn(metadata, q);
    var groupByMonth = /\b(month|monthly|monthwise|month-wise|month\s+wise)\b/i.test(q);
    var trend = asksForTrend(q);
    var grouped = {};
    var order = [];
    filtered.forEach(function (row, index) {
      var label = groupByMonth ? monthLabel(rowValue(row, dateColumn)) : "";
      if (!label) label = String(rowValue(row, dateColumn) || rowValue(row, resolveColumn(metadata, "", "category")) || "Row " + (index + 1));
      var dateValue = new Date(rowValue(row, dateColumn));
      var sortable = groupByMonth && !Number.isNaN(dateValue.getTime()) ? (dateValue.getFullYear() * 100 + dateValue.getMonth()) : index;
      if (!grouped[label]) {
        grouped[label] = { total: 0, key: sortable };
        order.push(label);
      }
      grouped[label].total += numericValue(rowValue(row, metric)) || 0;
    });
    order.sort(function (a, b) { return grouped[a].key - grouped[b].key; });
    order = order.slice(0, 18);
    if (!order.length) return [];
    var filterText = filter ? " for " + filter.value : "";
    var title = (groupByMonth ? "Monthly " : "") + (metric || "Value") + filterText;
    var chartType = manualChartType(q, trend ? "line" : "bar");
    var chartLabels = manualChartLabelsForType(chartType, order);
    var chartCaption = manualChartCaption(chartType, order, "Generated locally from the parsed sheet rows for: " + esc(q));
    return [{
      type: "chart",
      data: {
        type: chartType,
        title: title,
        labels: chartLabels.join(","),
        values: order.map(function (label) { return chartNumber(grouped[label].total); }).join(","),
        xAxisLabel: chartType === "scatter" ? (groupByMonth ? "Month position" : "Group position") : "",
        yAxisLabel: chartType === "scatter" ? metric : "",
        caption: chartCaption,
        color: "#2563eb",
        colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
      }
    }];
  }

  function manualNumericPair(metadata, question) {
    metadata = metadata || {};
    var rows = rowsForAnalytics(metadata);
    var text = String(question || "");
    var lower = text.toLowerCase();
    var numeric = Object.keys(metadata.numericStats || {});
    if (!rows.length || numeric.length < 2) return null;
    function resolveExplicitNumericPhrase(phrase) {
      phrase = String(phrase || "")
        .replace(/\b(calculate|correlation|correlate|pearson|between|and|with|of|for|the|a|an)\b/ig, " ")
        .replace(/[?.!,;:]+/g, " ")
        .replace(/\s+/g, " ")
        .trim();
      if (!phrase) return "";
      var phraseKey = columnKey(phrase);
      var exact = numeric.filter(function (column) { return columnKey(column) === phraseKey; })[0];
      if (exact) return exact;
      var contained = numeric.filter(function (column) {
        var key = columnKey(column);
        return key && (phraseKey.indexOf(key) >= 0 || key.indexOf(phraseKey) >= 0);
      }).sort(function (a, b) { return columnKey(b).length - columnKey(a).length; })[0];
      if (contained) return contained;
      var alias = metricColumnFromPhrase(metadata, phrase);
      return alias && numeric.indexOf(alias) >= 0 ? alias : "";
    }
    var between = text.match(/\bbetween\s+(.+?)\s+and\s+(.+?)(?:[.;?]|$)/i);
    if (between) {
      var xBetween = resolveExplicitNumericPhrase(between[1]);
      var yBetween = resolveExplicitNumericPhrase(between[2]);
      if (xBetween && yBetween && xBetween === yBetween && requestedChartType(text) === "scatter") return { x: xBetween, y: yBetween };
      return xBetween && yBetween && xBetween !== yBetween ? { x: xBetween, y: yBetween } : null;
    }
    var vs = text.match(/([^.;:,]+?)\s+v(?:s|ersus)\.?\s+([^.;:,]+)/i);
    if (vs) {
      var xFromVs = resolveExplicitNumericPhrase(vs[1]);
      var yFromVs = resolveExplicitNumericPhrase(vs[2]);
      if (xFromVs && yFromVs && xFromVs === yFromVs && requestedChartType(text) === "scatter") {
        return { x: xFromVs, y: yFromVs };
      }
      if (xFromVs && yFromVs && xFromVs !== yFromVs && numeric.indexOf(xFromVs) >= 0 && numeric.indexOf(yFromVs) >= 0) {
        return { x: xFromVs, y: yFromVs };
      }
      return null;
    }
    var mentioned = numeric.map(function (column) {
      var key = columnKey(column);
      var exact = lower.indexOf(String(column).toLowerCase());
      var compact = columnKey(text).indexOf(key);
      var phrase = metricColumnFromPhrase(metadata, column);
      return { column: column, index: exact >= 0 ? exact : compact, phrase: phrase };
    }).filter(function (item) {
      return item.index >= 0 || metricColumnFromPhrase(metadata, text) === item.column;
    }).sort(function (a, b) { return a.index - b.index; });
    if (mentioned.length >= 2 && mentioned[0].column !== mentioned[1].column) {
      return { x: mentioned[0].column, y: mentioned[1].column };
    }
    if (mentioned.length >= 1 && /\bvs\.?|versus|against|relationship|scatter\b/i.test(text)) {
      return { x: mentioned[0].column, y: mentioned[0].column };
    }
    return null;
  }

  function manualRelationshipGroupColumn(metadata, question) {
    var q = String(question || "");
    if (!/\b(by|based\s+on|group(?:ed)?\s+by|wise|per)\b/i.test(q)) return "";
    if (/\bper\s+order\b/i.test(q) && !/\b(by|based\s+on|group(?:ed)?\s+by|wise)\b/i.test(q)) return "";
    if (/\bdo\s+not\s+group\b|\bnot\s+by\b|\bwithout\s+group(?:ing)?\b/i.test(q)) return "";
    var group = resolveGroupColumn(metadata, q);
    return isForbiddenColumn(metadata, question, group) ? "" : group;
  }

  function manualRelationshipBlocks(metadata, question) {
    if (!metadata || requestedChartType(question) !== "scatter") return [];
    var rows = rowsForAnalytics(metadata);
    var pair = manualNumericPair(metadata, question);
    if (!rows.length || !pair) return [];
    var groupColumn = manualRelationshipGroupColumn(metadata, question);
    var aggregation = /\b(avg|average|mean|per\s+order|per\s+row)\b/i.test(String(question || "")) ? "average" : "sum";
    if (groupColumn) {
      var grouped = {};
      var groupOrder = [];
      rows.forEach(function (row) {
        var label = String(rowValue(row, groupColumn) || "Unknown");
        var x = numericValue(rowValue(row, pair.x));
        var y = numericValue(rowValue(row, pair.y));
        if (x === null || y === null) return;
        if (!grouped[label]) {
          grouped[label] = { x: 0, y: 0, count: 0 };
          groupOrder.push(label);
        }
        grouped[label].x += x;
        grouped[label].y += y;
        grouped[label].count += 1;
      });
      groupOrder.sort(function (a, b) {
        return (grouped[b].y - grouped[a].y) || String(a).localeCompare(String(b));
      });
      var groupedPoints = groupOrder.map(function (label) {
        var item = grouped[label];
        return {
          label: label,
          x: aggregation === "average" ? item.x / Math.max(1, item.count) : item.x,
          y: aggregation === "average" ? item.y / Math.max(1, item.count) : item.y,
          count: item.count
        };
      }).filter(function (point) { return Number.isFinite(point.x) && Number.isFinite(point.y); });
      if (!groupedPoints.length) return [];
      var groupedSummary = "Computed grouped relationship from " + rows.length + " parsed workbook rows grouped by `" + groupColumn + "`. X-axis uses " + aggregation + " `" + pair.x + "` and Y-axis uses " + aggregation + " `" + pair.y + "`.";
      var groupedTableRows = [["Group", pair.x + " (" + aggregation + ")", pair.y + " (" + aggregation + ")", "Rows"]].concat(groupedPoints.map(function (point) {
        return [point.label, chartNumber(point.x), chartNumber(point.y), String(point.count)];
      }));
      return [
        { type: "paragraph", data: { text: groupedSummary } },
        { type: "table", data: { withHeadings: true, rows: groupedTableRows } },
        {
          type: "chart",
          data: {
            type: "scatter",
            title: pair.x + " vs " + pair.y + " by " + groupColumn,
            labels: groupedPoints.map(function (point) { return chartNumber(point.x); }).join(","),
            values: groupedPoints.map(function (point) { return chartNumber(point.y); }).join(","),
            xAxisLabel: pair.x + " (" + aggregation + ")",
            yAxisLabel: pair.y + " (" + aggregation + ")",
            caption: "Each dot is one `" + groupColumn + "` group. Horizontal axis X = " + aggregation + " " + pair.x + "; vertical axis Y = " + aggregation + " " + pair.y + ". Group order: " + groupedPoints.map(function (point, index) { return (index + 1) + " = " + point.label; }).join("; ") + ". Generated from " + rows.length + " parsed workbook rows.",
            color: "#2563eb",
            colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
          }
        }
      ];
    }
    var sampled = evenlySampleRows(rows, 80);
    var points = sampled.map(function (row, index) {
      var x = numericValue(rowValue(row, pair.x));
      var y = numericValue(rowValue(row, pair.y));
      return x === null || y === null ? null : { label: "Row " + (index + 1), x: x, y: y };
    }).filter(Boolean);
    if (!points.length) return [];
    var summary = "Computed row-level relationship from " + rows.length + " parsed workbook rows. X-axis uses `" + pair.x + "` and Y-axis uses `" + pair.y + "`. No category grouping was applied.";
    var tableRows = [["Point", pair.x, pair.y]].concat(points.slice(0, 12).map(function (point) {
      return [point.label, chartNumber(point.x), chartNumber(point.y)];
    }));
    return [
      { type: "paragraph", data: { text: summary } },
      { type: "table", data: { withHeadings: true, rows: tableRows } },
      {
        type: "chart",
        data: {
          type: "scatter",
          title: pair.x + " vs " + pair.y,
          labels: points.map(function (point) { return chartNumber(point.x); }).join(","),
          values: points.map(function (point) { return chartNumber(point.y); }).join(","),
          xAxisLabel: pair.x,
          yAxisLabel: pair.y,
          caption: "Each dot is one displayed row/order. Horizontal axis X = " + pair.x + "; vertical axis Y = " + pair.y + ". Points shown: " + points.length + " evenly selected display points from " + rows.length + " parsed workbook rows.",
          color: "#2563eb",
          colors: "#2563eb,#16a34a,#f59e0b,#dc2626,#7c3aed,#0891b2"
        }
      }
    ];
  }

  function generatedNestedBlocks(metadata, analysis) {
    var charts = (analysis.recommendedCharts || []).filter(Boolean);
    var deep = analysis && analysis.analysisMode === "deep";
    var chartLimit = deep ? 10 : 3;
    var blocks = [
      { type: "heading", data: { level: 3, text: "Analysis Complete" } },
      { type: "list", data: { style: "unordered", items: listItems(analysis.summary, "Analysis completed for " + metadata.rowCount + " rows and " + metadata.columnCount + " columns.") } }
    ];
    if (deep) pushAnalysisSection(blocks, "Executive Summary", analysis.executiveSummary, 6);
    pushAnalysisSection(blocks, "Dataset Readiness", analysis.datasetReadiness, 3);
    pushAnalysisSection(blocks, "Executive KPIs", analysis.executiveKPIs, 8);
    pushAnalysisSection(blocks, "Data Quality", analysis.dataQualityIssues, 8, "No major data quality issue was returned from the analysis.");
    pushAnalysisSection(blocks, "Data Quality Impact", analysis.dataQualityImpact, deep ? 6 : 4);
    if (deep) pushAnalysisSection(blocks, "Missing Field Expectations", analysis.missingFieldExpectations, 6);
    pushAnalysisSection(blocks, "Analytics Insights", analysis.analytics, 10);
    if (!deep) pushAnalysisSection(blocks, "Top Findings", analysis.topFindings, 4);
    if (deep) {
      pushAnalysisSection(blocks, "Key Drivers", analysis.keyDrivers, 8);
      pushAnalysisSection(blocks, "Risk Flags", analysis.riskFlags, 8);
      pushAnalysisSection(blocks, "Opportunity Finder", analysis.opportunityFinder, 8);
      pushAnalysisSection(blocks, "Trends", analysis.trends, 8);
      pushAnalysisSection(blocks, "Anomalies", analysis.anomalies, 8);
      pushAnalysisSection(blocks, "Segment Breakdowns", analysis.segmentBreakdowns, 8);
      pushAnalysisSection(blocks, "Metric Glossary", analysis.metricGlossary, 8);
      pushAnalysisSection(blocks, "Confidence & Assumptions", analysis.confidenceAssumptions, 8);
    }
    blocks.push({ type: "heading", data: { level: 3, text: "Recommended Charts" } });
    blocks.push({ type: "list", data: { style: "unordered", items: charts.length ? charts.map(function (chart, index) {
      return { text: chartTitle(chart, index) + ": " + (chart.reason || "Recommended from the detected sheet structure."), level: 0 };
    }) : [{ text: "No chart recommendation was returned from the analysis.", level: 0 }] } });
    charts.slice(0, chartLimit).forEach(function (chart, index) {
      blocks.push({ type: "chart", data: buildChartData(metadata, chart, index) });
    });
    pushAnalysisSection(blocks, "Recommended Actions", analysis.recommendedActions, deep ? 8 : 5);
    if (deep) pushAnalysisSection(blocks, "Follow-up Questions", analysis.followUpQuestions, 8);
    else pushAnalysisSection(blocks, "Suggested Next Questions", analysis.suggestedNextQuestions, 3);
    return blocks;
  }

  function fallbackBlocks(metadata, analysis) {
    var charts = analysis.recommendedCharts || [];
    var deep = analysis && analysis.analysisMode === "deep";
    var firstChart = charts[0] || {};
    var previewRows = rowsForAnalytics(metadata).slice(0, 6);
    var fallbackX = safeDefaultDimensionColumn(metadata) || metadata.headings[0];
    var labels = previewRows.map(function (row, index) { return String(row[firstChart.xAxis] || row[fallbackX] || "Row " + (index + 1)); });
    var y = firstChart.yAxis || safeDefaultMetricColumn(metadata) || Object.keys(metadata.numericStats || {})[0] || metadata.headings[1] || metadata.headings[0];
    var values = previewRows.map(function (row) { return numericValue(row[y]) || 0; });
    var blocks = [
      { type: "heading", data: { level: 2, text: "Sheet Summary" } },
      { type: "list", data: { style: "unordered", items: listItems(analysis.summary, "Analyzed " + metadata.rowCount + " rows across " + metadata.columnCount + " columns.") } },
      { type: "heading", data: { level: 3, text: "Executive KPIs" } },
      { type: "list", data: { style: "unordered", items: listItems(analysis.executiveKPIs, "No executive KPI was returned from the analysis.") } },
      { type: "heading", data: { level: 3, text: "Data Quality" } },
      { type: "list", data: { style: "unordered", items: listItems(analysis.dataQualityIssues, "No major data quality issue was returned from the analysis.") } },
      { type: "heading", data: { level: 3, text: "Analytics Insights" } },
      { type: "list", data: { style: "unordered", items: listItems(analysis.analytics, "No analytics insight was returned from the analysis.") } },
      { type: "chart", data: { type: CHART_TYPES.indexOf(firstChart.chartType) >= 0 ? firstChart.chartType : "bar", title: firstChart.title || "Recommended Chart", labels: labels.join(","), values: values.join(","), caption: firstChart.reason || "Suggested from sheet metadata." } },
      { type: "table", data: { withHeadings: true, rows: [metadata.headings].concat(rowsForAnalytics(metadata).slice(0, 5).map(function (row) { return metadata.headings.map(function (heading) { return row[heading] || ""; }); })) } }
    ];
    blocks.splice.apply(blocks, [2, 0].concat((deep ? [
      { type: "heading", data: { level: 3, text: "Executive Summary" } },
      { type: "list", data: { style: "unordered", items: listItems(analysis.executiveSummary, "No executive summary was returned from the analysis.") } }
    ] : []).concat([
      { type: "heading", data: { level: 3, text: "Dataset Readiness" } },
      { type: "list", data: { style: "unordered", items: listItems(analysis.datasetReadiness, "No readiness score was returned from the analysis.") } }
    ])));
    pushAnalysisSection(blocks, "Data Quality Impact", analysis.dataQualityImpact, deep ? 6 : 4);
    if (deep) pushAnalysisSection(blocks, "Missing Field Expectations", analysis.missingFieldExpectations, 6);
    if (!deep) pushAnalysisSection(blocks, "Top Findings", analysis.topFindings, 4);
    if (deep) {
      pushAnalysisSection(blocks, "Key Drivers", analysis.keyDrivers, 8);
      pushAnalysisSection(blocks, "Risk Flags", analysis.riskFlags, 8);
      pushAnalysisSection(blocks, "Opportunity Finder", analysis.opportunityFinder, 8);
      pushAnalysisSection(blocks, "Trends", analysis.trends, 6);
      pushAnalysisSection(blocks, "Anomalies", analysis.anomalies, 6);
      pushAnalysisSection(blocks, "Segment Breakdowns", analysis.segmentBreakdowns, 6);
      pushAnalysisSection(blocks, "Metric Glossary", analysis.metricGlossary, 8);
      pushAnalysisSection(blocks, "Confidence & Assumptions", analysis.confidenceAssumptions, 8);
    }
    pushAnalysisSection(blocks, "Recommended Actions", (analysis.recommendedActions || []).concat(analysis.formattingSuggestions || []), deep ? 8 : 5);
    if (deep) pushAnalysisSection(blocks, "Follow-up Questions", analysis.followUpQuestions, 8);
    else pushAnalysisSection(blocks, "Suggested Next Questions", analysis.suggestedNextQuestions, 3);
    return blocks;
  }

  function followupBlocks(metadata, question, payload, count) {
    payload = payload || {};
    var deterministicResult = payload.force_ai_blocks ? null : runDeterministicAnalyticsPipeline(metadata, question);
    var deterministicBlocks = deterministicResult && deterministicResult.validation && deterministicResult.validation.ok ? deterministicResult.blocks : [];
    var blocks = deterministicBlocks.length ? deterministicBlocks : (payload.blocks || payload.editorBlocks || payload.answerBlocks || payload.createdBlocks || (payload.data && payload.data.blocks));
    if (!Array.isArray(blocks) || !blocks.length) {
      var analysis = payload.analysis || {};
      var answer = payload.answer || payload.response || payload.text || sentenceList(analysis.summary || analysis.analytics || payload.summary || payload.analytics, "");
      blocks = [{ type: "paragraph", data: { text: answer || "No answer was returned for this analytics question." } }];
    }
    blocks = blocks.filter(function (block) { return block && block.type && block.type !== "sheetAnalyzer"; });
    if (!blocks.some(function (block) { return block.type === "chart"; })) {
      blocks = blocks.concat(manualGraphBlocks(metadata, question));
    }
    return [{
      type: "toggle",
      data: {
        title: "Question " + count + ": " + String(question || "").trim(),
        open: true,
        blocks: blocks
      }
    }];
  }

  function narrativeInterpretationBlocks(metadata, question, payload, count) {
    payload = payload || {};
    var sourceBlocks = payload.blocks || payload.editorBlocks || payload.answerBlocks || payload.createdBlocks || (payload.data && payload.data.blocks);
    var blocks = [];
    if (Array.isArray(sourceBlocks)) {
      blocks = sourceBlocks.filter(function (block) {
        return block && (block.type === "paragraph" || block.type === "list" || block.type === "heading");
      }).map(function (block) {
        if (block.type !== "heading") return block;
        return { type: "paragraph", data: { text: block.data && block.data.text || "" } };
      }).filter(function (block) {
        return String(block && block.data && (block.data.text || JSON.stringify(block.data.items || "")) || "").trim();
      });
    }
    if (!blocks.length) {
      var analysis = payload.analysis || {};
      var answer = payload.answer || payload.response || payload.text || sentenceList(analysis.summary || analysis.analytics || payload.summary || payload.analytics, "");
      if (answer) blocks = [{ type: "paragraph", data: { text: answer } }];
    }
    if (!blocks.length) blocks = manualClarificationBlocks(metadata, question);
    blocks = formatNarrativeBlocks(blocks);
    return [{
      type: "toggle",
      data: {
        title: "Question " + count + ": " + String(question || "").trim(),
        open: true,
        blocks: blocks
      }
    }];
  }

  function formatNarrativeBlocks(blocks) {
    var output = [];
    (Array.isArray(blocks) ? blocks : []).forEach(function (block) {
      if (!block || block.type !== "paragraph") {
        output.push(block);
        return;
      }
      var text = String(block.data && block.data.text || "").replace(/\s+/g, " ").trim();
      if (text.length < 220) {
        output.push(block);
        return;
      }
      var sentences = text.match(/[^.!?]+[.!?]+(?:\s|$)|[^.!?]+$/g) || [text];
      var intro = sentences.shift() || "";
      if (intro.trim()) output.push({ type: "paragraph", data: { text: intro.trim() } });
      var items = sentences.map(function (sentence) {
        return sentence.trim();
      }).filter(Boolean).map(function (sentence) {
        return { text: sentence, level: 0 };
      });
      if (items.length) output.push({ type: "list", data: { style: "unordered", items: items } });
    });
    return output.length ? output : blocks;
  }

  function validateNarrativeInterpretationBlocks(blocks, metadata) {
    metadata = metadata || {};
    var text = flattenBlockText(blocks, { tableRows: 0, maxChars: 12000 });
    if (!text || text.trim().length < 40) return { ok: false, issues: ["Narrative interpretation did not return enough answer text."] };
    if (/invalid\s+json|no answer was returned|could not create a validated analytics answer|validation issues:/i.test(text)) {
      return { ok: false, issues: ["Narrative interpretation returned an error instead of an answer."] };
    }
    if (nestedTables(blocks).length || nestedCharts(blocks).length) {
      return { ok: false, issues: ["Narrative interpretation must not insert table or chart blocks; it must use grounded paragraph/list blocks only."] };
    }
    var unsupportedColumns = unsupportedNarrativeColumns(metadata, text);
    if (unsupportedColumns.length) {
      return { ok: false, issues: ["Narrative interpretation referenced unsupported columns: " + unsupportedColumns.join(", ") + "."] };
    }
    var unsupportedNumbers = unsupportedNarrativeNumbers(metadata, text);
    if (unsupportedNumbers.length) {
      return { ok: false, issues: ["Narrative interpretation cited unsupported numeric values: " + unsupportedNumbers.slice(0, 8).join(", ") + "."] };
    }
    return { ok: true, issues: [] };
  }

  function unsupportedNarrativeColumns(metadata, text) {
    var headings = (metadata.headings || []).map(String);
    var allowed = {};
    headings.forEach(function (heading) { allowed[columnKey(heading)] = true; });
    var unsupported = [];
    var matches = String(text || "").match(/`([^`]+)`/g) || [];
    matches.forEach(function (match) {
      var name = match.replace(/`/g, "").trim();
      var key = columnKey(name);
      if (key && !allowed[key] && unsupported.indexOf(name) === -1) unsupported.push(name);
    });
    return unsupported;
  }

  function unsupportedNarrativeNumbers(metadata, text) {
    var allowed = narrativeAllowedNumberSet(metadata);
    var unsupported = [];
    var matches = String(text || "").match(/(?:₹|\$)?\b\d{1,3}(?:,\d{3})+(?:\.\d+)?%?|\b\d+(?:\.\d+)?%?/g) || [];
    matches.forEach(function (raw) {
      var stripped = raw.replace(/[₹$,%\s]/g, "");
      var number = Number(stripped);
      if (!Number.isFinite(number)) return;
      if (number >= 0 && number <= 20 && !/%/.test(raw) && !/\./.test(raw)) return;
      var keys = narrativeNumberKeys(number, /%/.test(raw));
      var ok = keys.some(function (key) { return allowed[key]; });
      if (!ok && unsupported.indexOf(raw) === -1) unsupported.push(raw);
    });
    return unsupported;
  }

  function narrativeAllowedNumberSet(metadata) {
    metadata = metadata || {};
    var allowed = {};
    function add(value) {
      var number = Number(value);
      if (!Number.isFinite(number)) return;
      narrativeNumberKeys(number, false).forEach(function (key) { allowed[key] = true; });
      narrativeNumberKeys(number, true).forEach(function (key) { allowed[key] = true; });
    }
    add(metadata.rowCount || rowsForAnalytics(metadata).length || 0);
    add(metadata.columnCount || (metadata.headings || []).length || 0);
    add(metadata.duplicateRows || 0);
    add(metadata.emptyRows || 0);
    Object.keys(metadata.missingValues || {}).forEach(function (column) {
      add((metadata.missingValues || {})[column]);
      var rows = Number(metadata.rowCount || rowsForAnalytics(metadata).length || 0) || 0;
      if (rows) add(((Number((metadata.missingValues || {})[column]) || 0) / rows) * 100);
    });
    Object.keys(metadata.uniqueCounts || {}).forEach(function (column) { add((metadata.uniqueCounts || {})[column]); });
    Object.keys(metadata.numericStats || {}).forEach(function (column) {
      var stats = (metadata.numericStats || {})[column] || {};
      ["count", "nonEmpty", "min", "max", "sum", "average", "avg", "median"].forEach(function (key) { add(stats[key]); });
    });
    return allowed;
  }

  function narrativeNumberKeys(number, percent) {
    var values = [number, Math.round(number), chartNumber(number)];
    if (percent) values.push(chartNumber(number) + "%");
    return values.map(function (value) {
      return String(value).replace(/[,%\s]/g, "").toLowerCase();
    });
  }

  function nestedCharts(blocks) {
    var charts = [];
    function walk(list) {
      (Array.isArray(list) ? list : []).forEach(function (block) {
        if (!block || typeof block !== "object") return;
        if (block.type === "chart") charts.push(block);
        var data = block.data || {};
        if (Array.isArray(data.blocks)) walk(data.blocks);
      });
    }
    walk(blocks);
    return charts;
  }

  function validateManualAnalyticsBlocks(metadata, question, blocks, contract) {
    var q = String(question || "");
    var issues = [];
    var charts = nestedCharts(blocks);
    var tables = nestedTables(blocks);
    var allText = flattenBlockText(blocks);
    if (q) allText = allText.split(q.toLowerCase()).join("");
    if (/\bcolumn was not found in this sheet\b/i.test(allText)) return [];
    var headers = tableHeadersText(tables);
    var explicitType = requestedChartType(q);
    contract = contract || manualIntentContract(metadata, q);
    if (/^(?:clarification|missing_column_guard)$/i.test(String(contract.intentType || ""))) return [];
    if (contract.intentType !== "pivot_matrix" && asksForGraph(q) && !charts.length) {
      issues.push("Question asks for a chart/graph, but no chart block was created.");
    }
    if (contract.intentType !== "pivot_matrix" && explicitType && !charts.some(function (chart) { return String(chart.data && chart.data.type || "").toLowerCase() === explicitType.toLowerCase(); })) {
      issues.push("Question explicitly asks for " + explicitType + " chart, but the created chart type does not match.");
    }
    charts.forEach(function (chart) {
      var data = chart.data || {};
      var labelCount = splitCSV(data.labels || "").length;
      if (labelCount > 80) {
        issues.push("Chart has " + labelCount + " labels, which is not readable. Aggregate to month/category or limit to top groups.");
      }
      if ((data.type === "comparison" || data.type === "stackedBar") && String(data.values || "").indexOf(";") >= 0 && !String(data.seriesLabels || "").trim()) {
        issues.push("Multi-series chart is missing series labels, so users cannot tell which color represents which metric/group.");
      }
    });
    (contract.forbiddenColumns || []).forEach(function (column) {
      var pattern = new RegExp("\\b" + String(column).replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\b", "i");
      var groupedPattern = new RegExp("\\b" + String(column).replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\s+(count|share|by|distribution)", "i");
      if (groupedPattern.test(allText) || pattern.test(headers)) {
        issues.push("Prompt explicitly excludes `" + column + "`, but output still uses it as a displayed/grouped column.");
      }
    });
    if (/\btable|data|show|tell|count|percentage|percent|wise|by\b/i.test(q)) {
      var hasTable = false;
      (function walk(list) {
        (Array.isArray(list) ? list : []).forEach(function (block) {
          if (block && block.type === "table") hasTable = true;
          if (block && block.data && Array.isArray(block.data.blocks)) walk(block.data.blocks);
        });
      })(blocks);
      if (!hasTable && !charts.length) issues.push("Question asks for analytics data, but no table or chart block was created.");
    }
    if (contract.intentType === "pivot_matrix" || contract.intentType === "cross_tab" || contract.intentType === "cross_tab_count") {
      var pivot = contract.pivot || pivotMatrixIntent(metadata, q) || {};
      var rowDimensions = pivot.rowColumn === "__month__" ? ["Month"] : ((Array.isArray(pivot.rowColumns) && pivot.rowColumns.length) ? pivot.rowColumns : [pivot.rowColumn]);
      var valueHeader = pivot.valueMode === "count" || contract.metric === "__row_count" ? "" : String(pivot.valueColumn || "").toLowerCase();
      if (!tables.length) issues.push("Matrix/pivot output requires a table.");
      rowDimensions.forEach(function (dimension) {
        if (dimension && !analyticsHeaderOrTextHasDimension(headers, allText, dimension)) issues.push("Matrix/pivot output is missing row dimension `" + dimension + "`.");
      });
      if (pivot.columnColumn && !analyticsHeaderOrTextHasDimension(headers, allText, pivot.columnColumn) && !pivotColumnDimensionIsRepresented(metadata, pivot, blocks)) issues.push("Matrix/pivot output does not preserve requested column dimension `" + pivot.columnColumn + "`.");
      if (valueHeader && headers.indexOf(valueHeader) < 0 && allText.indexOf(valueHeader) < 0) issues.push("Matrix/pivot output does not mention requested value metric `" + valueHeader + "`.");
      if (!contract.rowLevelAllowed && /\border\s*id|row\s+\d+\b/i.test(headers + " " + allText)) issues.push("Matrix/pivot output must not list raw row/order records.");
      if (contract.requiresChart && charts.length) {
        charts.forEach(function (chart) {
          var data = chart.data || {};
          if ((data.type === "comparison" || data.type === "stackedBar") && !String(data.seriesLabels || "").trim()) {
            issues.push("Matrix chart is missing seriesLabels for the column dimension.");
          }
        });
      }
    }
    if (contract.intentType === "combination_ranking") {
      if (!tables.length) issues.push("Combination ranking requires a table.");
      var combo = contract.combination || {};
      (combo.columns || []).forEach(function (column) {
        if (headers.indexOf(String(column).toLowerCase()) < 0) issues.push("Combination ranking is missing required dimension `" + column + "`.");
      });
      ["revenue", "profit", "order count"].forEach(function (field) {
        if (headers.indexOf(field) < 0) issues.push("Combination ranking is missing `" + field + "`.");
      });
      if (/\bmargin\b/i.test(q) && headers.indexOf("margin") < 0) issues.push("Combination ranking is missing profit margin.");
    }
    if (contract.intentType === "formula_metric") {
      if (!tables.length) issues.push("Formula metric output requires a table.");
      if (/\bprofit\s+margin\b/i.test(q) && !/\bprofit\s+margin\b/i.test(headers + " " + allText)) issues.push("Formula intent requires Profit Margin %, not revenue share.");
      if (/\bdiscount\s*%|discount\s+percentage\b/i.test(q) && !/\bdiscount\s*%|discount percentage\b/i.test(headers + " " + allText)) issues.push("Formula intent requires Discount %.");
      if (/\bprofit\s+share|share\s+by\s+total\s+profit\b/i.test(q) && !/\bprofit\s+share\b/i.test(headers + " " + allText)) issues.push("Formula intent requires Profit Share %.");
      if (/\bnet\s+revenue\b/i.test(q) && !/\bnet\s+revenue\b/i.test(headers + " " + allText)) issues.push("Formula intent requires Net Revenue.");
    }
    if (contract.intentType === "distribution_share") {
      var groupText = (allText + " " + headers).toLowerCase();
      if (contract.groupBy && groupText.indexOf(String(contract.groupBy).toLowerCase()) < 0 && !/\bgroup\b/i.test(groupText)) {
        issues.push("Intent contract requires grouping by `" + contract.groupBy + "`, but output does not clearly mention that grouping.");
      }
      if (contract.groupBy && /^(date|month)$/i.test(columnKey(contract.groupBy)) && /\b(payment|method|department|category|status|city|zone|segment)\b/i.test(q)) {
        issues.push("Distribution question names a categorical target, but output grouped by `" + contract.groupBy + "` instead.");
      }
      if (contract.groupBy && columnKey(contract.groupBy).indexOf("payment") >= 0 && /\bexact\s+date\s+count|date\s+share|date\s+count\b/i.test(allText)) {
        issues.push("Payment-method distribution was incorrectly converted into raw Date counts.");
      }
      if (contract.timeGroupBy) {
        if (!/\b(month|monthly|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\b/i.test(allText)) {
          issues.push("Intent contract requires a monthly/time breakdown, but the output does not show month-level groups.");
        }
        if (contract.groupBy && !headers.match(new RegExp(String(contract.groupBy).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i")) && !/\bseries\s+colors\b/i.test(allText)) {
          issues.push("Monthly distribution must keep `" + contract.groupBy + "` as comparison series or table columns.");
        }
      }
      (contract.forbiddenColumns || []).forEach(function (column) {
        if (new RegExp("\\b" + String(column).replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\s+(count|share)", "i").test(allText)) {
          issues.push("Prompt explicitly excludes `" + column + "`, but output grouped by it.");
        }
      });
      if (!/\b(count|orders?|rows?)\b/i.test(headers + " " + allText)) {
        issues.push("Intent contract requires order/row counts for grouped distribution.");
      }
      if (contract.percentageRequired && !/\b(percent|percentage|share|%)\b/i.test(headers + " " + allText)) {
        issues.push("Intent contract requires percentage/share output.");
      }
      if (!contract.rowLevelAllowed && (/\b(row\s+\d+|top\s+20)\b/i.test(allText) || /\border\s*id|orderid\b/i.test(headers))) {
        issues.push("Intent contract forbids row-level OrderID listing/sorting for grouped share/distribution questions.");
      }
      var groupCount = contract.groupBy ? ((metadata.uniqueCounts || {})[contract.groupBy] || 0) : 0;
      if (groupCount && tableRowCount(tables) > Math.max(groupCount + 2, 12) && /\border\s*id|orderid\b/i.test(allText)) {
        issues.push("Distribution output has too many row-level records instead of one row per group.");
      }
      if (!contract.timeGroupBy && contract.groupBy && tableRowCount(tables) > 80 && !/date|month/i.test(columnKey(contract.groupBy))) {
        issues.push("Distribution output has too many groups for the requested categorical summary; use the named category instead of raw dates or rows.");
      }
    }
    if (contract.intentType === "rank_sort") {
      if (contract.metric && allText.indexOf(String(contract.metric).toLowerCase()) < 0) issues.push("Intent contract requires sort metric `" + contract.metric + "`.");
      if (contract.groupBy && allText.indexOf(String(contract.groupBy).toLowerCase()) < 0) issues.push("Intent contract requires sorting within/grouped by `" + contract.groupBy + "`.");
    }
    if (contract.intentType === "time_trend") {
      if (!/\b(month|date|time|trend|year|week|day)\b/i.test(allText)) issues.push("Intent contract requires a time/date trend output.");
      (contract.mentionedNumeric || []).forEach(function (metric) {
        if (allText.indexOf(String(metric).toLowerCase()) < 0) issues.push("Time trend output is missing requested metric `" + metric + "`.");
      });
    }
    if (contract.intentType === "month_over_month_condition") {
      if (!/\brevenue\s+change|profit\s+change|previous\s+month|month-over-month|month\s+over\s+month\b/i.test(headers + " " + allText)) {
        issues.push("Month-over-month condition output must include Revenue Change, Profit Change, and previous-month comparison.");
      }
      if (!tables.length) issues.push("Month-over-month condition output requires a table.");
    }
    if (contract.intentType === "outlier_rows") {
      if (!tables.length) issues.push("Outlier output requires a row-level table.");
      if (headers.indexOf("discount") < 0) issues.push("Outlier output must include the requested Discount metric.");
      if (/\bprofit\b/i.test(q) && headers.indexOf("profit") < 0) issues.push("Outlier output must include Profit.");
      if (/\brevenue\b/i.test(q) && headers.indexOf("revenue") < 0) issues.push("Outlier output must include Revenue.");
    }
    if ((contract.intentType === "grouped_metric" || contract.intentType === "formula_metric") && contract.groupBy) {
      if (allText.indexOf(String(contract.groupBy).toLowerCase()) < 0) issues.push("Intent contract requires grouping by `" + contract.groupBy + "`.");
      if (contract.metric && allText.indexOf(String(contract.metric).toLowerCase()) < 0) issues.push("Intent contract requires metric `" + contract.metric + "`.");
    }
    var pair = manualNumericPair(metadata, q);
    if (explicitType === "scatter" && pair && charts.length) {
      var chartText = charts.map(function (chart) {
        var data = chart.data || {};
        return [data.title, data.xAxisLabel, data.yAxisLabel, data.caption].join(" ").toLowerCase();
      }).join(" ");
      if (chartText.indexOf(String(pair.x).toLowerCase()) < 0) issues.push("Scatter chart does not clearly use X-axis field `" + pair.x + "`.");
      if (chartText.indexOf(String(pair.y).toLowerCase()) < 0) issues.push("Scatter chart does not clearly use Y-axis field `" + pair.y + "`.");
      var groupColumn = manualRelationshipGroupColumn(metadata, q);
      if (groupColumn && chartText.indexOf(String(groupColumn).toLowerCase()) < 0) {
        issues.push("Question asks for grouping by `" + groupColumn + "`, but the scatter output does not clearly use that grouping.");
      }
      if (groupColumn && /\bno\s+(category\s+)?grouping\s+was\s+applied\b/i.test(chartText)) {
        issues.push("Question asks for grouping by `" + groupColumn + "`, but the answer says no grouping was applied.");
      }
    }
    return { ok: !issues.length, issues: issues };
  }

  function manualRepairQuestion(question, validation, attempt, contract, qualityRules) {
    var storedRules = qualityRulesText(qualityRules);
    return [
      String(question || ""),
      "",
      "Intent contract:",
      JSON.stringify(contract || {}, null, 2),
      "",
      storedRules ? "Stored quality rules from previous validations:\n" + storedRules : "",
      "",
      "Repair instructions for AI validation loop:",
      (validation.issues || []).map(function (issue, index) { return (index + 1) + ". " + issue; }).join("\n"),
      "Create the corrected answer and blocks only. Keep the original user intent. Do not switch chart type, grouping, or metrics away from the prompt.",
      "Repair attempt " + attempt + " of 3."
    ].join("\n");
  }

  function validationFailureBlocks(question, issues, count) {
    return [{
      type: "toggle",
      data: {
        title: "Question " + count + ": " + String(question || "").trim(),
        open: true,
        blocks: [{
          type: "paragraph",
          data: {
            text: "I could not create a validated analytics answer for this prompt, so no unchecked result was inserted. Validation issues: " + (issues || []).join(" ")
          }
        }]
      }
    }];
  }

  function isValidationFailureAnswer(blocks) {
    var text = flattenBlockText(blocks || []);
    return /I could not create a validated analytics answer|Validation issues:|needs a clearer metric or grouping/i.test(text);
  }

  function insertBlocks(editor, block, blocks) {
    var hostBlock = block.closest(".rbe-block");
    var index = hostBlock && editor.indexOfBlock ? editor.indexOfBlock(hostBlock) + 1 : undefined;
    blocks.forEach(function (item, offset) {
      if (!item || !item.type) return;
      editor.insertBlock(item.type, item.data || {}, index == null ? undefined : index + offset);
    });
    editor.changed(true);
  }

  BlockWriteAI.__sheetAnalyzerCore = Object.assign({}, BlockWriteAI.__sheetAnalyzerCore || {}, {
    buildAnalyticsExecutionPlan: buildAnalyticsExecutionPlan,
    analyticsEngineVersion: analyticsEngineVersion,
    validateExecutionPlan: validateExecutionPlan,
    makeExecutionResult: makeExecutionResult,
    runAnalyticsValidators: runAnalyticsValidators,
    runDeterministicAnalyticsPipeline: runDeterministicAnalyticsPipeline,
    runDeterministicAnalyticsPipelineAsync: runDeterministicAnalyticsPipelineAsync,
    analyticsSemanticProfile: analyticsSemanticProfile,
    manualIntentContract: manualIntentContract,
    canonicalManualAnalyticsDecision: canonicalManualAnalyticsDecision,
    manualContractCreditEstimate: manualContractCreditEstimate,
    normalizeStoredIntentContract: normalizeStoredIntentContract,
    buildChartData: buildChartData,
    formulaRegistry: SHEET_FORMULA_REGISTRY,
    validatorRegistry: ANALYTICS_VALIDATOR_REGISTRY
  });

  BlockWriteAI.registerTool("sheetAnalyzer", {
    label: "AI Sheet Analyzer",
    hint: "Upload CSV/XLSX and generate sheet insights",
    icon: "table",
    premiumFeature: "sheet_analyzer",
    defaultData: { status: "empty" },
    render: function (body, data) {
      injectStyles();
      var editor = this;
      var wrap = el("div", "rbe-sheet-card");
      wrap.innerHTML =
        '<div class="rbe-sheet-head"><div class="rbe-sheet-title">' + svgIcon("table", "AI Sheet Analyzer") + '<span>AI Sheet Analyzer</span></div><div class="rbe-sheet-badges"><span class="rbe-sheet-badge is-premium">Premium</span><span class="rbe-sheet-badge" data-cost>Analysis: 15 credits</span></div></div>' +
        '<label class="rbe-sheet-drop"><input class="rbe-sheet-file" type="file" accept=".csv,.xlsx,text/csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet">' + svgIcon("cloud-arrow-up", "Upload") + '<strong>Drag and drop or click here</strong><span>to upload sheet</span><small>CSV and XLSX only</small></label>' +
        '<div class="rbe-sheet-error" hidden></div><div class="rbe-sheet-meta" hidden></div><div class="rbe-sheet-columns" hidden></div>' +
        '<div class="rbe-sheet-actions"><label class="rbe-sheet-toggle" title="Deep Analysis uses full-sheet computed analytics, not full raw-row AI upload."><input class="rbe-sheet-deep" type="checkbox">Deep Analysis</label><button type="button" class="rbe-sheet-analyze" data-primary disabled title="Analyze sheet" aria-label="Analyze sheet">' + svgIcon("sparkles", "Analyze sheet") + '<span>Analyze</span></button><button type="button" class="rbe-sheet-insert" hidden title="Insert blocks into editor" aria-label="Insert blocks into editor">' + svgIcon("circle-plus", "Insert blocks into editor") + '<span>Insert Blocks into Editor</span></button><button type="button" class="rbe-sheet-delete" hidden title="Delete upload" aria-label="Delete upload">' + svgIcon("trash-can", "Delete upload") + '<span>Delete Upload</span></button></div><div class="rbe-sheet-result" hidden></div>' +
        '<div class="rbe-sheet-nested"><div class="rbe-sheet-nested-head"><strong>Nested blocks</strong></div><div class="rbe-toggle-nested-canvas rbe-sheet-nested-canvas"></div><div class="rbe-sheet-manual" hidden><div class="rbe-sheet-manual-field"><div class="rbe-sheet-manual-row"><input class="rbe-sheet-manual-input" type="text" placeholder="Ask manual analytics question"><span class="rbe-sheet-manual-credit" hidden></span></div><div class="rbe-sheet-prompt-lint" hidden></div><div class="rbe-sheet-suggest" hidden></div></div><button type="button" class="rbe-sheet-manual-btn" title="Analytics" aria-label="Analytics">' + svgIcon("sparkles", "Analytics") + '</button></div></div>';
      body.appendChild(wrap);
      var previewRender = isSheetPreviewRender(editor);
      var hostBlock = wrap.closest(".rbe-block");
      data = data || {};
      var draftId = data.draftId || (hostBlock && hostBlock.dataset && hostBlock.dataset.id) || "";
      data = Object.assign({}, data || {}, { draftId: draftId });
      var draft = previewRender ? null : readDraft(data);
      data = previewRender ? (data || {}) : mergeDraftData(data, draft);
      if (!previewRender && !hasSheetState(data)) {
        data.status = "empty";
        data.metadata = null;
        data.fileId = null;
        data.analysis = null;
        data.blocks = [];
        data.nestedBlocks = [];
      } else if (previewRender && !hasSheetState(data) && !hasNestedContent(data.nestedBlocks)) {
        data.status = "empty";
        data.metadata = null;
        data.fileId = null;
        data.analysis = null;
        data.blocks = [];
        data.nestedBlocks = [];
      }
      var state = {
        draftId: data.draftId || draftId,
        status: data.status || "empty",
        metadata: data.metadata || null,
        fileId: data.fileId || null,
        analysis: data.analysis || null,
        blocks: data.blocks || [],
        nestedBlocks: Array.isArray(data.nestedBlocks) ? data.nestedBlocks : []
      };
      var nestedCanvas = wrap.querySelector(".rbe-sheet-nested-canvas");
      var analysisTimer = null;
      var mainAnalysisRunning = false;
      var followupCount = 0;
      function setMainAnalysisRunning(running) {
        mainAnalysisRunning = !!running;
        var analyze = wrap.querySelector(".rbe-sheet-analyze");
        var deep = wrap.querySelector(".rbe-sheet-deep");
        var deleteButton = wrap.querySelector(".rbe-sheet-delete");
        var toggle = wrap.querySelector(".rbe-sheet-toggle");
        if (analyze) analyze.disabled = mainAnalysisRunning || !state.metadata;
        if (deep) deep.disabled = mainAnalysisRunning;
        if (deleteButton) deleteButton.disabled = mainAnalysisRunning || !state.fileId;
        if (toggle) toggle.classList.toggle("is-disabled", mainAnalysisRunning);
      }
      function syncState() {
        var hostBlock = wrap.closest(".rbe-block");
        if (hostBlock) hostBlock.__rbeSheetState = state;
        if (hasSheetState(state)) writeDraft(state);
      }
      function removeAnalyzerBlock() {
        var hostBlock = wrap.closest(".rbe-block");
        if (!hostBlock) return false;
        if (editor && typeof editor.handleBlockAction === "function") {
          editor.handleBlockAction(hostBlock, "delete");
        } else {
          hostBlock.remove();
          if (editor && typeof editor.changed === "function") editor.changed(true);
        }
        return true;
      }
      syncState();
      (Array.isArray(data.nestedBlocks) && data.nestedBlocks.length ? data.nestedBlocks : [{ type: "paragraph", data: { text: "" } }]).forEach(function (child) {
        var childType = child && child.type && child.type !== "sheetAnalyzer" ? child.type : "paragraph";
        if (childType === "heading" && /^Question\s+\d+:/i.test(String((child.data || {}).text || ""))) {
          followupCount = Math.max(followupCount, Number(String(child.data.text).match(/\d+/)[0]) || 0);
        } else if (childType === "toggle" && /^Question\s+\d+:/i.test(String((child.data || {}).title || ""))) {
          followupCount = Math.max(followupCount, Number(String(child.data.title).match(/\d+/)[0]) || 0);
        } else if (childType === "heading" && /^Analytics Question$/i.test(String((child.data || {}).text || ""))) {
          followupCount += 1;
        }
        var nestedBlock = editor.createBlock(childType, child.data || editor.defaultBlockData(childType), child.id);
        nestedBlock.classList.add("rbe-nested-block");
        nestedCanvas.appendChild(nestedBlock);
      });
      function setBusy(text) {
        if (editor && typeof editor.setShellProgress === "function") editor.setShellProgress(true, "AI Sheet Analyzer: " + text);
      }
      function clearBusy() {
        if (editor && typeof editor.setShellProgress === "function") editor.setShellProgress(false, "");
      }
      function notify(message, type) {
        if (!message) return;
        if (editor && typeof editor.showToast === "function") editor.showToast(message, type || "warning");
      }
      function showError(message) {
        clearBusy();
        var error = wrap.querySelector(".rbe-sheet-error");
        error.hidden = true;
        error.textContent = "";
        notify(message || "Sheet Analyzer request failed.", "error");
      }
      function clearError() {
        var error = wrap.querySelector(".rbe-sheet-error");
        error.hidden = true;
        error.textContent = "";
      }
      function status(text, done) {
        if (text) setBusy(text);
        if (done) clearBusy();
      }
      function progress(value, text) {
        if (text) setBusy(text);
      }
      function appendAnalysisLine(text) {
        var result = wrap.querySelector(".rbe-sheet-result");
        var lines = result.querySelector(".rbe-sheet-live-lines");
        if (!lines) {
          result.hidden = false;
          result.innerHTML = '<h4>Analysis in progress</h4><ul class="rbe-sheet-live-lines"></ul>';
          lines = result.querySelector(".rbe-sheet-live-lines");
        }
        var last = lines.lastElementChild;
        if (last) last.classList.add("is-complete");
        var item = document.createElement("li");
        item.innerHTML = '<span class="rbe-sheet-live-check" aria-hidden="true"></span><span></span>';
        item.querySelector("span:last-child").textContent = text;
        lines.appendChild(item);
      }
      function startAnalysisLog() {
        if (analysisTimer) clearInterval(analysisTimer);
        var meta = state.metadata || {};
        var deepMode = wrap.querySelector(".rbe-sheet-deep").checked;
        var numericColumns = Object.keys(meta.numericStats || {});
        var profile = analyticsSemanticProfile(meta);
        var domainLabel = profile && profile.domain && profile.domain !== "generic" ? profile.domain : "sheet";
        var matchedColumns = (meta.headings || []).filter(function (heading) {
          return /date|patient|encounter|department|diagnosis|procedure|doctor|readmission|icu|outcome|claim|insurance|bill|cost|payment|wait|stay|satisfaction|order|product|customer|category|revenue|profit|discount|quantity|region|city|status/i.test(String(heading || ""));
        }).slice(0, 9);
        var domainStep = "I am preparing " + domainLabel + " KPIs when matching columns such as " + (matchedColumns.join(", ") || "the uploaded sheet's detected business columns") + " exist.";
        var steps = deepMode ? [
          "Deep Analysis may take 1-5 minutes for large sheets because it computes full-dataset analytics before AI interpretation.",
          "I am computing full-sheet analytics locally from " + (meta.rowCount || 0) + " rows without sending raw rows to AI.",
          "I am calculating missing values, duplicates, data types, medians, outliers, correlations, trends, grouped summaries, and chart-ready datasets.",
          domainStep,
          "I am sending only the compact analytics summary to the AI analyst for strict JSON interpretation.",
          "I will create executive KPIs, data quality, analytics insights, chart recommendations, trends/anomalies, and recommended actions after the response is valid."
        ] : [
          "I am starting with compact full-dataset analytics from " + (meta.rowCount || 0) + " parsed rows, not a 25-row sample.",
          "I found " + (meta.rowCount || 0) + " rows and " + (meta.columnCount || 0) + " columns to review.",
          "I am checking headings, detected column types, missing values, and duplicate rows.",
          "I am reviewing numeric fields such as " + (numericColumns.slice(0, 5).join(", ") || "the detected numeric columns") + " for min, max, average, and total patterns.",
          "I am comparing categorical and date-like columns to decide which chart types make sense.",
          "I am asking the AI analyst for a strict JSON response using only this prepared metadata.",
          "I will create the nested analysis blocks and recommended chart blocks after the response is valid."
        ];
        var index = 0;
        var result = wrap.querySelector(".rbe-sheet-result");
        result.hidden = false;
        result.innerHTML = "";
        appendAnalysisLine(steps[index++]);
        analysisTimer = setInterval(function () {
          if (index >= steps.length) {
            clearInterval(analysisTimer);
            analysisTimer = null;
            return;
          }
          appendAnalysisLine(steps[index++]);
        }, 900);
      }
      function stopAnalysisLog() {
        if (analysisTimer) clearInterval(analysisTimer);
        analysisTimer = null;
        Array.prototype.slice.call(wrap.querySelectorAll(".rbe-sheet-live-lines li")).forEach(function (item) {
          item.classList.add("is-complete");
        });
      }
      function failAnalysisLog(message) {
        if (analysisTimer) clearInterval(analysisTimer);
        analysisTimer = null;
        var lines = wrap.querySelector(".rbe-sheet-live-lines");
        if (!lines) return;
        var items = Array.prototype.slice.call(lines.querySelectorAll("li"));
        items.slice(0, -1).forEach(function (item) {
          item.classList.add("is-complete");
        });
        var failed = items[items.length - 1];
        if (!failed) {
          appendAnalysisLine(message || "AI analysis failed.");
          failed = lines.lastElementChild;
        }
        failed.classList.remove("is-complete");
        failed.classList.add("is-error");
        var text = failed.querySelector("span:last-child");
        if (text) text.textContent = message || "AI analysis failed.";
      }
      function normalizeTableZeroCells(blocks) {
        return (blocks || []).map(function (block) {
          if (!block || typeof block !== "object") return block;
          var copy = Object.assign({}, block);
          copy.data = Object.assign({}, block.data || {});
          if (copy.type === "table" && copy.data && Array.isArray(copy.data.rows)) {
            copy.data.rows = copy.data.rows.map(function (row) {
              return (row || []).map(function (cell) {
                return cell === 0 ? "0" : cell;
              });
            });
          }
          if (Array.isArray(copy.data.blocks)) copy.data.blocks = normalizeTableZeroCells(copy.data.blocks);
          return copy;
        });
      }
      function renderNestedBlocks(blocks) {
        blocks = normalizeTableZeroCells(blocks || []);
        nestedCanvas.innerHTML = "";
        (blocks && blocks.length ? blocks : [{ type: "paragraph", data: { text: "" } }]).forEach(function (child) {
          if (!child || !child.type || child.type === "sheetAnalyzer") return;
          var nestedBlock = editor.createBlock(child.type, child.data || editor.defaultBlockData(child.type), child.id);
          nestedBlock.classList.add("rbe-nested-block");
          nestedCanvas.appendChild(nestedBlock);
        });
        state.nestedBlocks = nestedCanvas && editor.serializeBlock ? Array.prototype.slice.call(nestedCanvas.children).map(editor.serializeBlock.bind(editor)) : (blocks || []);
        syncState();
      }
      function appendNestedBlocks(blocks) {
        blocks = normalizeTableZeroCells(blocks || []);
        (blocks || []).forEach(function (child) {
          if (!child || !child.type || child.type === "sheetAnalyzer") return;
          var nestedBlock = editor.createBlock(child.type, child.data || editor.defaultBlockData(child.type), child.id);
          nestedBlock.classList.add("rbe-nested-block");
          nestedCanvas.appendChild(nestedBlock);
        });
        state.nestedBlocks = nestedCanvas && editor.serializeBlock ? Array.prototype.slice.call(nestedCanvas.children).map(editor.serializeBlock.bind(editor)) : (state.nestedBlocks || []).concat(blocks || []);
        syncState();
      }
      function showManualAnalytics(show) {
        var manual = wrap.querySelector(".rbe-sheet-manual");
        manual.hidden = !show;
        if (show) renderManualPromptAssist();
      }
      function confirmRerunAnalysis() {
        return new Promise(function (resolve) {
          var existing = document.querySelector(".rbe-sheet-modal-backdrop");
          if (existing) existing.remove();
          var backdrop = el("div", "rbe-sheet-modal-backdrop", {
            role: "presentation",
            html: '<div class="rbe-sheet-modal modal-dialog" role="dialog" aria-modal="true" aria-labelledby="rbe-sheet-rerun-title">' +
              '<div class="rbe-sheet-modal-head modal-header"><strong id="rbe-sheet-rerun-title">Run a New Analysis?</strong><button type="button" class="rbe-sheet-modal-close" aria-label="Close">' + svgIcon("xmark", "Close") + '</button></div>' +
              '<div class="rbe-sheet-modal-body modal-body"><p>This sheet already has an analysis. Save the current document version first if you want to keep this result before running a new one.</p><small>Running a new analysis will replace the analysis shown inside this AI Sheet Analyzer block.</small></div>' +
              '<div class="rbe-sheet-modal-foot modal-footer"><button type="button" data-action="save">Save version first</button><button type="button" data-primary data-action="rerun">Run new analysis</button></div>' +
              '</div>'
          });
          function close(value) {
            document.removeEventListener("keydown", onKey);
            backdrop.remove();
            resolve(value);
          }
          function onKey(event) {
            if (event.key === "Escape") close("cancel");
          }
          backdrop.addEventListener("click", function (event) {
            var action = event.target && event.target.closest && event.target.closest("[data-action]");
            if (action) {
              close(action.getAttribute("data-action"));
              return;
            }
            if (event.target === backdrop || (event.target.closest && event.target.closest(".rbe-sheet-modal-close"))) close("cancel");
          });
          document.addEventListener("keydown", onKey);
          document.body.appendChild(backdrop);
          var primary = backdrop.querySelector("[data-action='rerun']");
          if (primary) primary.focus();
        });
      }
      function showStaleRestoreModal() {
        var existing = document.querySelector(".rbe-sheet-modal-backdrop");
        if (existing) existing.remove();
        var backdrop = el("div", "rbe-sheet-modal-backdrop", {
          role: "presentation",
          html: '<div class="rbe-sheet-modal modal-dialog" role="dialog" aria-modal="true" aria-labelledby="rbe-sheet-stale-title">' +
            '<div class="rbe-sheet-modal-head modal-header"><strong id="rbe-sheet-stale-title">Sheet Data Needs Refresh</strong><button type="button" class="rbe-sheet-modal-close" aria-label="Close">' + svgIcon("xmark", "Close") + '</button></div>' +
            '<div class="rbe-sheet-modal-body modal-body"><p>The sheet metadata was restored, but the parsed row data needed for manual analytics is missing after refresh.</p><small>Clear this stale analyzer data, then upload/analyze the sheet again for reliable answers.</small></div>' +
            '<div class="rbe-sheet-modal-foot modal-footer"><button type="button" data-action="cancel">Close</button><button type="button" data-primary data-action="clear-all">' + svgIcon("trash-can", "Clear all sheet data") + '<span>Clear All</span></button></div>' +
            '</div>'
        });
        function close() {
          document.removeEventListener("keydown", onKey);
          backdrop.remove();
        }
        function onKey(event) {
          if (event.key === "Escape") close();
        }
        backdrop.addEventListener("click", function (event) {
          var action = event.target && event.target.closest && event.target.closest("[data-action]");
          if (action && action.getAttribute("data-action") === "clear-all") {
            clearDraft(state);
            resetUploadState();
            removeAnalyzerBlock();
            notify("Stale AI Sheet Analyzer block removed.", "success");
            close();
            return;
          }
          if (action || event.target === backdrop || (event.target.closest && event.target.closest(".rbe-sheet-modal-close"))) close();
        });
        document.addEventListener("keydown", onKey);
        document.body.appendChild(backdrop);
        var primary = backdrop.querySelector("[data-action='clear-all']");
        if (primary) primary.focus();
      }
      function refreshCost() {
        wrap.querySelector("[data-cost]").textContent = wrap.querySelector(".rbe-sheet-deep").checked ? "Deep Analysis: 25 credits" : "Analysis: 15 credits";
        if (state.metadata) renderMeta();
      }
      function renderMeta() {
        if (!state.metadata) return;
        var meta = wrap.querySelector(".rbe-sheet-meta");
        meta.hidden = false;
        meta.innerHTML = [
          ["File", state.metadata.fileName],
          ["Rows", state.metadata.rowCount],
          ["Columns", state.metadata.columnCount],
          ["Duplicates", state.metadata.duplicateRows]
        ].map(function (item) { return '<div><span>' + esc(item[0]) + '</span><strong>' + esc(item[1]) + '</strong></div>'; }).join("");
        var cols = wrap.querySelector(".rbe-sheet-columns");
        cols.hidden = false;
        var highlightMissing = !!wrap.querySelector(".rbe-sheet-deep").checked;
        cols.innerHTML = '<div class="rbe-sheet-column is-head"><strong>Column names</strong><span>Type</span><span>Missing found</span></div>' + (state.metadata.columns || []).map(function (column) {
          var missingCount = Number(column.missing || 0);
          return '<div class="rbe-sheet-column' + (highlightMissing && missingCount > 0 ? ' has-missing' : '') + '"><strong title="' + esc(column.name) + '">' + esc(column.name) + '</strong><span>' + esc(column.type) + '</span><span>Missing ' + esc(column.missing) + '</span></div>';
        }).join("");
        setMainAnalysisRunning(mainAnalysisRunning);
        wrap.querySelector(".rbe-sheet-delete").hidden = !state.fileId;
      }
      function renderResult() {
        if (!state.analysis) return;
        var result = wrap.querySelector(".rbe-sheet-result");
        result.hidden = false;
        result.innerHTML = '<h4>Analysis Complete</h4>' + list(state.analysis.summary) + '<h4>Recommended Charts</h4>' + list((state.analysis.recommendedCharts || []).map(function (chart) { return (chart.title || chart.chartType || "Chart") + ": " + (chart.reason || "Recommended"); }));
        wrap.querySelector(".rbe-sheet-insert").hidden = false;
        showManualAnalytics(true);
      }
      function resetUploadState() {
        state.status = "empty";
        state.metadata = null;
        state.fileId = null;
        state.analysis = null;
        state.blocks = [];
        state.nestedBlocks = [];
        state.promptBank = null;
        state.promptBankSummary = null;
        followupCount = 0;
        var meta = wrap.querySelector(".rbe-sheet-meta");
        if (meta) {
          meta.hidden = true;
          meta.innerHTML = "";
        }
        var cols = wrap.querySelector(".rbe-sheet-columns");
        if (cols) {
          cols.hidden = true;
          cols.innerHTML = "";
        }
        var result = wrap.querySelector(".rbe-sheet-result");
        if (result) {
          result.hidden = true;
          result.innerHTML = "";
        }
        if (nestedCanvas) nestedCanvas.innerHTML = "";
        setMainAnalysisRunning(false);
        wrap.querySelector(".rbe-sheet-insert").hidden = true;
        wrap.querySelector(".rbe-sheet-insert").disabled = false;
        wrap.querySelector(".rbe-sheet-delete").hidden = true;
        var fileInput = wrap.querySelector(".rbe-sheet-file");
        if (fileInput) fileInput.value = "";
        showManualAnalytics(false);
        updateManualButtonState(true);
        renderManualPromptAssist(true);
      }
      wrap.querySelector(".rbe-sheet-deep").addEventListener("change", refreshCost);
      function handleFile(file) {
        if (!file) return;
        clearError();
        parseFile(file, progress).then(function (metadata) {
          state.metadata = metadata;
          progress(85, "Saving upload metadata");
          return request("upload", { file_name: metadata.fileName, file_size: metadata.fileSize, metadata: metadataForAi(metadata, true) });
        }).then(function (payload) {
          state.fileId = payload.file_id;
          state.promptBankSummary = payload.prompt_bank || null;
          state.status = "uploaded";
          progress(100, "Ready for AI analysis");
          clearBusy();
          renderMeta();
          notify("Sheet uploaded and parsed. Ready for AI analysis.", "success");
          syncState();
          editor.changed(true);
          request("prompt_bank", { file_id: state.fileId, limit: 1000 }).then(function (bank) {
            state.promptBank = bank && Array.isArray(bank.contracts) ? bank : null;
            state.promptBankSummary = bank ? { domain: bank.domain, prompt_count: bank.prompt_count } : state.promptBankSummary;
            syncState();
            renderManualPromptAssist(false);
          }).catch(function (error) {
            if (global.console && console.warn) console.warn("AI Sheet Analyzer prompt bank fetch failed:", error);
          });
        }).catch(function (error) {
          showError(error.message);
        });
      }
      var drop = wrap.querySelector(".rbe-sheet-drop");
      drop.addEventListener("dragenter", function (event) {
        event.preventDefault();
        event.stopPropagation();
        drop.classList.add("is-dragging");
      });
      drop.addEventListener("dragover", function (event) {
        event.preventDefault();
        event.stopPropagation();
        drop.classList.add("is-dragging");
      });
      drop.addEventListener("dragleave", function (event) {
        event.stopPropagation();
        drop.classList.remove("is-dragging");
      });
      drop.addEventListener("drop", function (event) {
        event.preventDefault();
        event.stopPropagation();
        drop.classList.remove("is-dragging");
        handleFile(event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]);
      });
      wrap.querySelector(".rbe-sheet-file").addEventListener("change", function (event) {
        handleFile(event.target.files && event.target.files[0]);
      });
      function runMainAnalysis() {
        clearError();
        wrap.querySelector(".rbe-sheet-result").hidden = true;
        setMainAnalysisRunning(true);
        startAnalysisLog();
        status("Running AI analysis", false);
        var deepAnalysis = !!wrap.querySelector(".rbe-sheet-deep").checked;
        var usageStartedAt = Date.now();
        var usageState = null;
        startSheetAnalysisUsage(editor, state.metadata, deepAnalysis).then(function (usage) {
          usageState = usage;
          var analyzerUsageId = usageEventId(usage);
          var analyzerAction = sheetAnalysisBillingAction(deepAnalysis);
          return analyzeSheetInChunks({
            file_id: state.fileId,
            metadata: state.metadata,
            deep_analysis: deepAnalysis,
            create_blocks: true,
            analysis_chunk_size: ANALYSIS_CHUNK_SIZE,
            usage_event_id: analyzerUsageId,
            external_usage_event_id: analyzerUsageId,
            billing_action: analyzerAction,
            billing_route: analyzerAction,
            credits_required: sheetAnalysisBillingCredits(deepAnalysis)
          }, function (chunk) {
          appendAnalysisLine((chunk.localAnalytics ? "Full-dataset analytics engine processed " : "Compact analysis prepared ") + chunk.rowCount + " rows before the AI call.");
          status(chunk.localAnalytics ? "Computing full-dataset analytics" : "Running AI analysis", false);
          });
        }).then(function (payload) {
          status("Creating blocks", true);
          stopAnalysisLog();
          state.analysis = payload.analysis;
          state.blocks = payload.blocks && payload.blocks.length ? payload.blocks : fallbackBlocks(state.metadata, state.analysis || {});
          state.nestedBlocks = generatedNestedBlocks(state.metadata, state.analysis || {});
          logQualityLineage({
            feature: "sheet_analyzer",
            route: deepAnalysis ? "deep_analysis" : "analysis",
            status: "passed",
            prompt: deepAnalysis ? "Deep Analysis" : "Analysis",
            intent: { intentType: "initial_sheet_analysis", createBlocks: true },
            execution_plan: {
              version: "2026-08-foundation-1",
              intentType: "initial_sheet_analysis",
              rowCount: (state.metadata && state.metadata.rowCount) || 0,
              columnCount: (state.metadata && state.metadata.columnCount) || 0,
              chunkSize: ANALYSIS_CHUNK_SIZE,
              deep: deepAnalysis
            },
            rules_executed: ["high-cardinality-chart-guard", "chart-table-consistency"],
            rules_failed: [],
            repairs: {},
            rows_used: (state.metadata && state.metadata.rowCount) || 0,
            result: extractResultSnapshot(state.nestedBlocks),
            validation: { ok: true, issues: [] }
          });
          return completeSheetAnalysisUsage(editor, usageState, state.metadata, deepAnalysis, payload, usageStartedAt).catch(function (billingError) {
            if (global.console && console.warn) console.warn("AI Sheet Analyzer usage completion failed:", billingError);
          }).then(function () {
            state.status = "analyzed";
            renderNestedBlocks(state.nestedBlocks);
            showManualAnalytics(true);
            clearBusy();
            renderResult();
            setMainAnalysisRunning(false);
            notify("AI Sheet Analyzer analysis completed.", "success");
            if (payload.warning && !(state.nestedBlocks && state.nestedBlocks.length)) notify(payload.warning, "warning");
            syncState();
            editor.changed(true);
          });
        }).catch(function (error) {
          var finishFailure = usageState ? failSheetAnalysisUsage(editor, usageState, state.metadata, deepAnalysis, error, usageStartedAt).catch(function (billingError) {
            if (global.console && console.warn) console.warn("AI Sheet Analyzer usage failure update failed:", billingError);
          }) : Promise.resolve();
          finishFailure.then(function () {
            failAnalysisLog(error.message);
            setMainAnalysisRunning(false);
            showError(error.message);
          });
        });
      }
      wrap.querySelector(".rbe-sheet-analyze").addEventListener("click", function () {
        if (!state.metadata) return;
        if (state.analysis) {
          confirmRerunAnalysis().then(function (choice) {
            if (choice === "save") {
              notify("Please save the current document version or create a new AI Sheet Analyzer block for the next analysis.", "warning");
              setMainAnalysisRunning(false);
              return;
            }
            if (choice === "rerun") runMainAnalysis();
          });
          return;
        }
        runMainAnalysis();
      });
      wrap.querySelector(".rbe-sheet-insert").addEventListener("click", function () {
        insertBlocks(editor, wrap, state.blocks || []);
        wrap.querySelector(".rbe-sheet-insert").disabled = true;
      });
      function updateManualButtonState(forceDisabled) {
        var input = wrap.querySelector(".rbe-sheet-manual-input");
        var button = wrap.querySelector(".rbe-sheet-manual-btn");
        if (!input || !button) return;
        button.disabled = !!forceDisabled || !!state.manualPromptAssistBusy || !String(input.value || "").trim();
      }
      function setManualPromptAssistBusy(busy) {
        var button = wrap.querySelector(".rbe-sheet-manual-btn");
        state.manualPromptAssistBusy = !!busy;
        if (button) {
          button.classList.toggle("is-checking", !!busy);
          button.title = busy ? "Checking prompt" : "Analytics";
          button.setAttribute("aria-label", busy ? "Checking prompt" : "Analytics");
        }
        updateManualButtonState();
      }
      function promptBankMatchScore(question, contract) {
        var qKey = promptIntentKey(question);
        var prompt = String(contract && contract.prompt || "");
        var haystack = promptIntentKey([prompt, contract && contract.intentType, contract && contract.metric, contract && contract.groupBy, contract && contract.aggregation].join(" "));
        if (!qKey || !haystack) return 0;
        if (!promptBankContractPreservesQuestion(question, contract)) return 0;
        function requiresMetric(regex, aliases) {
          if (!regex.test(String(question || ""))) return false;
          return !aliases.some(function (alias) { return haystack.indexOf(alias) >= 0; });
        }
        if (requiresMetric(/\brevenue\b/i, ["revenue", "sales", "totalbill", "billing", "bill", "amount"])) return 0;
        if (requiresMetric(/\bunit\s*price|unitprice\b/i, ["unitprice", "unit", "price"])) return 0;
        if (requiresMetric(/\bprofit\b/i, ["profit", "margin"])) return 0;
        if (requiresMetric(/\bcogs\b|cost\s+of\s+goods/i, ["cogs", "costofgoods"])) return 0;
        if (requiresMetric(/\b(age|patient\s+age)\b/i, ["age", "patientage"])) return 0;
        if (requiresMetric(/\bwait\s*time\b/i, ["waittime", "waittimeminutes"])) return 0;
        if (requiresMetric(/\blength\s+of\s+stay|los\b/i, ["lengthofstay", "lengthofstaydays", "los"])) return 0;
        if (qKey === promptIntentKey(prompt)) return 1000;
        var qTokens = qKey.split(/\s+/).filter(function (token) { return token.length > 2; });
        if (!qTokens.length) return 0;
        var hits = qTokens.reduce(function (sum, token) { return sum + (haystack.indexOf(token) >= 0 ? 1 : 0); }, 0);
        return hits / qTokens.length;
      }
      function comparableSuggestionIntent(intent) {
        intent = String(intent || "").toLowerCase();
        if (/^(grouped_analytics|grouped_metric|grouped_count)$/.test(intent)) return "grouped";
        if (/^(scalar_metric|scalar_count|scalar_rate)$/.test(intent)) return "scalar";
        return intent;
      }
      function comparableAggregation(aggregation) {
        aggregation = String(aggregation || "").toLowerCase();
        if (/^(sum|total)$/.test(aggregation)) return "sum";
        if (/^(avg|average|mean)$/.test(aggregation)) return "average";
        if (/^(count|row_count|filtered_count)$/.test(aggregation)) return "count";
        return aggregation;
      }
      function liveManualIntentContract(question) {
        state.manualPromptContractCache = state.manualPromptContractCache || {};
        var key = [state.fileId || "", state.metadata && state.metadata.rowCount || 0, state.metadata && state.metadata.columnCount || 0, promptIntentKey(question)].join("|");
        if (!state.manualPromptContractCache[key]) {
          state.manualPromptContractCache[key] = manualIntentContract(state.metadata, question);
        }
        return state.manualPromptContractCache[key];
      }
      function promptBankContractPreservesQuestion(question, contract) {
        if (!state.metadata || !contract) return false;
        var typed = liveManualIntentContract(question);
        var stored = normalizeStoredIntentContract(state.metadata, question, Object.assign({}, contract.contract || contract, {
          prompt: contract.prompt,
          intentType: contract.intentType,
          metric: contract.metric,
          groupBy: contract.groupBy,
          aggregation: contract.aggregation,
          outputType: contract.outputType,
          requiresAI: contract.requiresAI,
          creditEstimate: contract.creditEstimate || contract.credit_estimate
        }));
        if (!typed || !stored) return false;
        var typedIntent = comparableSuggestionIntent(typed.intentType);
        var storedIntent = comparableSuggestionIntent(stored.intentType);
        if (typedIntent && storedIntent && typedIntent !== storedIntent) return false;
        var typedMetric = columnKey(typed.metric || "");
        var storedMetric = columnKey(stored.metric || "");
        if (typedMetric && typedMetric !== "__row_count" && storedMetric && storedMetric !== typedMetric) return false;
        var typedGroup = columnKey(typed.groupBy || "");
        var storedGroup = columnKey(stored.groupBy || "");
        if (typedGroup && storedGroup && storedGroup !== typedGroup) return false;
        var typedAgg = comparableAggregation(typed.aggregation || aggregationMode(question));
        var storedAgg = comparableAggregation(stored.aggregation || contract.aggregation || "");
        if (typedAgg && storedAgg && typedAgg !== storedAgg) return false;
        if (typed.percentageRequired && !stored.percentageRequired && storedIntent !== "grouped_share") return false;
        if (!typed.percentageRequired && storedIntent === "grouped_share") return false;
        return validateSuggestionPreservesOriginalIntent(state.metadata, question, contract.prompt || "").ok;
      }
      function findPromptBankContract(question, requireHighConfidence) {
        var bank = state.promptBank && Array.isArray(state.promptBank.contracts) ? state.promptBank.contracts : [];
        if (!bank.length || !question) return null;
        var qKey = promptIntentKey(question);
        var qTokens = qKey.split(/\s+/).filter(function (token) { return token.length > 2; });
        var best = null;
        bank.forEach(function (contract) {
          var prompt = String(contract && contract.prompt || "");
          var promptKey = promptIntentKey(prompt);
          var haystack = promptIntentKey([prompt, contract && contract.intentType, contract && contract.metric, contract && contract.groupBy, contract && contract.aggregation].join(" "));
          if (qKey !== promptKey && qTokens.length && !qTokens.some(function (token) { return haystack.indexOf(token) >= 0; })) return;
          var score = promptBankMatchScore(question, contract);
          if (!score) return;
          if (!promptBankContractPreservesQuestion(question, contract)) return;
          if (!best || score > best.score || (score === best.score && Number(contract.creditEstimate || contract.credit_estimate || 99) < Number(best.contract.creditEstimate || best.contract.credit_estimate || 99))) {
            best = { contract: contract, score: score };
          }
        });
        if (!best) return null;
        if (best.score === 1000 || !requireHighConfidence || best.score >= 0.72) {
          var normalized = normalizeStoredIntentContract(state.metadata, question, Object.assign({}, best.contract.contract || best.contract, {
            prompt: best.contract.prompt,
            source: "sheet_prompt_bank",
            intentType: best.contract.intentType,
            metric: best.contract.metric,
            groupBy: best.contract.groupBy,
            aggregation: best.contract.aggregation,
            outputType: best.contract.outputType,
            requiresAI: best.contract.requiresAI,
            creditEstimate: best.contract.creditEstimate || best.contract.credit_estimate
          }));
          if (normalized) normalized.matchScore = best.score;
          return normalized;
        }
        return null;
      }
      function refreshPromptBank() {
        if (!state.fileId) return Promise.resolve(null);
        return request("prompt_bank", { file_id: state.fileId, limit: 1000 }).then(function (bank) {
          state.promptBank = bank || null;
          return bank;
        }).catch(function () {
          return null;
        });
      }
      function saveManualPromptContract(question, contract, credits, validation) {
        if (!state.fileId || !state.metadata || !contract || validation && validation.ok === false) return Promise.resolve(null);
        var normalized = normalizeStoredIntentContract(state.metadata, question, Object.assign({}, contract, {
          prompt: question,
          source: contract.source || "manual_success_contract"
        }));
        if (!normalized || !normalized.deterministicRoute) return Promise.resolve(null);
        var deterministicEstimate = estimateManualPromptCredits(state.metadata, question);
        var savedCredits = normalized.source === "manual_ai_success_contract"
          ? (parseInt(credits, 10) || deterministicEstimate.credits || 0)
          : deterministicEstimate.credits;
        normalized.creditEstimate = Math.max(0, Math.min(25, parseInt(savedCredits, 10) || 0));
        return request("prompt_bank_save", {
          file_id: state.fileId,
          contracts: [{
            prompt: question,
            intentType: normalized.intentType,
            metric: normalized.metric,
            groupBy: normalized.groupBy,
            aggregation: normalized.aggregation,
            outputType: normalized.requiresTable ? "table" : "paragraph",
            requiresAI: normalized.source === "manual_ai_success_contract",
            creditEstimate: normalized.creditEstimate,
            confidence: 1,
            contract: normalized
          }]
        }).then(function () {
          var bank = state.promptBank && Array.isArray(state.promptBank.contracts) ? state.promptBank.contracts : [];
          bank.unshift({
            prompt: question,
            intentType: normalized.intentType,
            metric: normalized.metric,
            groupBy: normalized.groupBy,
            aggregation: normalized.aggregation,
            outputType: normalized.requiresTable ? "table" : "paragraph",
            creditEstimate: normalized.creditEstimate,
            requiresAI: normalized.source === "manual_ai_success_contract",
            confidence: 1,
            contract: normalized
          });
          state.promptBank = Object.assign({}, state.promptBank || {}, { contracts: bank.slice(0, 1000) });
          return refreshPromptBank();
        }).catch(function (error) {
          if (global.console && console.warn) console.warn("Manual prompt contract save failed:", error);
          return null;
        });
      }
      function promptBankSuggestions(question) {
        var bank = state.promptBank && Array.isArray(state.promptBank.contracts) ? state.promptBank.contracts : [];
        if (!bank.length || !question) return [];
        var qKey = promptIntentKey(question);
        var qTokens = qKey.split(/\s+/).filter(function (token) { return token.length > 2; });
        if (!qTokens.length) return [];
        return bank.map(function (contract, index) {
          var prompt = String(contract.prompt || "");
          var promptKey = promptIntentKey(prompt);
          var haystack = promptIntentKey([prompt, contract && contract.intentType, contract && contract.metric, contract && contract.groupBy, contract && contract.aggregation].join(" "));
          if (qKey !== promptKey && !qTokens.some(function (token) { return haystack.indexOf(token) >= 0; })) return null;
          var score = promptBankMatchScore(question, contract);
          if (!score) return null;
          var normalizedContract = canonicalManualAnalyticsDecision(state.metadata, question, contract.contract || contract).contract;
          if (!normalizedContract || !manualContractsCompatible(liveManualIntentContract(question), normalizedContract)) return null;
          var estimate = manualContractCreditEstimate(state.metadata, question, normalizedContract);
          prompt = manualContractQuestion(state.metadata, question, normalizedContract);
          return {
            title: "Saved sheet prompt",
            prompt: prompt,
            reason: "Matched from this sheet's generated prompt bank (" + (normalizedContract.intentType || contract.intentType || "analytics") + ").",
            estimate: estimate,
            tone: promptSuggestionTone(prompt, index),
            deterministicRoute: normalizedContract.deterministicRoute || normalizedContract.intentType || "prompt_bank_contract",
            intentType: normalizedContract.intentType,
            metric: normalizedContract.metric,
            groupBy: normalizedContract.groupBy,
            aggregation: normalizedContract.aggregation,
            creditEstimate: estimate.credits,
            contract: normalizedContract,
            executionPlan: { contract: Object.assign({}, normalizedContract, {
              prompt: prompt,
              source: "sheet_prompt_bank",
              intentType: normalizedContract.intentType,
              metric: normalizedContract.metric,
              groupBy: normalizedContract.groupBy,
              aggregation: normalizedContract.aggregation,
              outputType: normalizedContract.outputType,
              requiresAI: normalizedContract.requiresAI,
              creditEstimate: estimate.credits
            }), source: "sheet_prompt_bank" },
            validation: { ok: true, issues: [] },
            _score: score
          };
        }).filter(function (suggestion) {
          return suggestion && suggestionPreservesLiveContract(state.metadata, question, suggestion);
        }).sort(function (a, b) {
          return b._score - a._score || a.estimate.credits - b.estimate.credits;
        }).slice(0, 3);
      }
      function detectedIntentSuggestion(question) {
        if (!state.metadata || !question) return [];
        var contract = manualIntentContract(state.metadata, question);
        var intent = String(contract && contract.intentType || "").toLowerCase();
        var route = String(contract && contract.deterministicRoute || routeForStoredIntentContract(contract) || "");
        if (!contract || !route || intent === "general" || intent === "narrative_interpretation") return [];
        var prompt = String(manualContractQuestion(state.metadata, question, contract) || question).replace(/\s+/g, " ").trim();
        var estimate = estimateManualPromptCredits(state.metadata, prompt);
        var detectedContract = Object.assign({}, contract, {
          prompt: prompt,
          source: "detected_intent_contract",
          deterministicRoute: route,
          creditEstimate: estimate.credits
        });
        return [{
          title: "Detected analytics prompt",
          prompt: prompt,
          reason: "Uses the detected sheet intent (" + (contract.intentType || "analytics") + ") without changing the question.",
          estimate: estimate,
          tone: promptSuggestionTone(prompt, 0),
          deterministicRoute: route,
          executionPlan: { contract: detectedContract, source: "detected_intent_contract" },
          validation: { ok: true, issues: [] },
          resultSnapshot: null
        }];
      }
      function promptLintItems(question) {
        var q = String(question || "").trim();
        if (!state.metadata || !q) return [];
        var items = [];
        function add(label, message, severity) {
          if (!label || items.some(function (item) { return item.label.toLowerCase() === String(label).toLowerCase(); })) return;
          items.push({ label: label, message: message, severity: severity || "warning" });
        }
        if (/\bhighest\s+to\s+lowest|lowest\s+to\s+highest|ascending|descending|sort(?:ed)?\b/i.test(q)) {
          add("sort", "This sorts the result. Make sure the metric and grouping column are clear, for example: SUM TotalBill_INR by Department.", "info");
        }
        if (/\b(?:by|per|group(?:ed)?\s+by|based\s+on|for\s+each|wise)\b/i.test(q)) {
          var groups = promptGroupColumns(state.metadata, q, "");
          if (groups.length > 1) {
            add("groups: " + groups.map(columnLabel).join(", "), "Detected grouping by `" + groups.join("`, `") + "`. These columns exist in the uploaded sheet.", "info");
          } else {
            var group = groups[0] || resolveGroupColumn(state.metadata, q);
            add(group ? "group: " + columnLabel(group) : "grouping unclear", group ? "Detected grouping by `" + group + "`. This column exists in the uploaded sheet." : "Grouping was requested, but no clear existing sheet column was found. Add a column name like Department, City, Status, or Category.", group ? "info" : "error");
          }
        }
        if (/\bperformance\b/i.test(q)) {
          add("performance", "Performance is broad. Choose a metric such as revenue, bill, profit, cost, satisfaction, wait time, score, or count.", "warning");
        }
        if (/\b(validate\s+table\s+and\s+chart\s+consistency|chart\s+consistency)\b/i.test(q) && !asksForGraph(q)) {
          add("chart validation", "Use chart consistency only when a chart is requested. For a simple table answer this wording is unnecessary.", "warning");
        }
        var missingMetric = missingRequestedMetric(state.metadata, q);
        if (missingMetric) {
          add(missingMetric, missingMetric + " column was not found in this sheet. Use an available column or ask for interpretation instead of calculation.", "error");
        }
        var mentioned = promptMentionedColumns(state.metadata, q);
        var metric = manualMetricColumn(state.metadata, q);
        if (/\b(total|sum|average|avg|mean|median|min|max|rate|percentage|percent|correlation|iqr|above|below|greater|less)\b/i.test(q) && !missingMetric && !metric && !/\b(count|how\s+many|rows?|columns?|duplicate|missing|unique|distinct)\b/i.test(q)) {
          add("metric unclear", "The calculation needs a clear numeric metric. Use an exact sheet column name if possible.", "error");
        }
        if (mentioned.length > 1 && /\b(total|sum|average|avg|mean)\b/i.test(q)) {
          var numericMentioned = mentioned.filter(function (column) { return Object.prototype.hasOwnProperty.call(state.metadata.numericStats || {}, column); });
          if (numericMentioned.length > 1) add("multiple metrics", "Multiple numeric columns are mentioned. The system will use the one closest to the requested calculation; remove extra metric names if that is not intended.", "warning");
        }
        if (/\bfrom\s+my\s+prompt|required\s+columns|cannot\s+be\s+confidently\s+resolved\b/i.test(q)) {
          add("interpretation", "This reads like an interpretation or clarification request. It should return guidance, not force table/group validation.", "info");
        }
        return items.slice(0, 6);
      }
      function renderPromptLint(question, forceHide) {
        var lintBox = wrap.querySelector(".rbe-sheet-prompt-lint");
        if (!lintBox) return;
        lintBox.innerHTML = "";
        var items = forceHide ? [] : promptLintItems(question);
        if (!items.length) {
          lintBox.hidden = true;
          return;
        }
        lintBox.hidden = false;
        items.forEach(function (item) {
          var chip = document.createElement("span");
          chip.className = "rbe-sheet-lint-chip";
          chip.setAttribute("data-severity", item.severity || "warning");
          chip.textContent = item.label;
          chip.title = item.message;
          lintBox.appendChild(chip);
        });
      }
      function renderManualPromptAssist(forceHide) {
        var input = wrap.querySelector(".rbe-sheet-manual-input");
        var credit = wrap.querySelector(".rbe-sheet-manual-credit");
        var box = wrap.querySelector(".rbe-sheet-suggest");
        if (!input || !credit || !box) return;
        var question = String(input.value || "").trim();
        if (!question || forceHide || !state.metadata) {
          credit.hidden = true;
          credit.textContent = "";
          box.hidden = true;
          box.innerHTML = "";
          renderPromptLint("", true);
          return;
        }
        renderPromptLint(question, false);
        var estimate = estimateManualPromptCredits(state.metadata, question);
        liveManualIntentContract(question);
        var suggestions = dedupeManualSuggestions(promptBankSuggestions(question).concat(analyticsPromptSuggestions(state.metadata, question)));
        if (!suggestions.length) suggestions = detectedIntentSuggestion(question);
        suggestions = filterPreviouslyAcceptedSuggestions(suggestions, state.acceptedManualSuggestions, question);
        state.manualSuggestionRuleCache = state.manualSuggestionRuleCache || {};
        state.manualSuggestionRulePending = state.manualSuggestionRulePending || {};
        var ruleKey = "sheet_analyzer:" + question.toLowerCase();
        var cachedRules = state.manualSuggestionRuleCache[ruleKey];
        if (!cachedRules && !suggestions.length) {
          box.hidden = true;
          box.innerHTML = "";
          if (!state.manualSuggestionRulePending[ruleKey]) {
            state.manualSuggestionRulePending[ruleKey] = true;
            fetchQualityRules("sheet_analyzer", question).then(function (rules) {
              state.manualSuggestionRuleCache[ruleKey] = Array.isArray(rules) ? rules : [];
            }).catch(function () {
              state.manualSuggestionRuleCache[ruleKey] = [];
            }).finally(function () {
              state.manualSuggestionRulePending[ruleKey] = false;
              var current = wrap.querySelector(".rbe-sheet-manual-input");
              if (current && String(current.value || "").trim() === question) renderManualPromptAssist(false);
            });
          }
          return;
        }
        suggestions = dedupeManualSuggestions(filterSuggestionsByQualityRules(suggestions, cachedRules || [], question));
        suggestions = suggestions.filter(function (suggestion) {
          return suggestion && suggestionPreservesLiveContract(state.metadata, question, suggestion);
        });
        suggestions = filterPreviouslyAcceptedSuggestions(suggestions, state.acceptedManualSuggestions, question);
        if (!suggestions.length) suggestions = detectedIntentSuggestion(question);
        var exactSuggestion = suggestions.filter(function (suggestion) {
          return promptIntentKey(suggestion && suggestion.prompt) === promptIntentKey(question);
        }).sort(function (a, b) {
          return (a.estimate && a.estimate.credits || 99) - (b.estimate && b.estimate.credits || 99);
        })[0];
        var bestCreditSuggestion = exactSuggestion || suggestions.slice().sort(function (a, b) {
          return (b._score || 0) - (a._score || 0) || (a.estimate && a.estimate.credits || 99) - (b.estimate && b.estimate.credits || 99);
        })[0];
        if (bestCreditSuggestion && bestCreditSuggestion.estimate && Number(bestCreditSuggestion.estimate.credits) > 0) {
          estimate = { credits: Number(bestCreditSuggestion.estimate.credits), level: bestCreditSuggestion.title || "prompt-bank", modes: [] };
        }
        credit.hidden = false;
        credit.textContent = "~" + estimate.credits + " credits";
        credit.title = "Estimated manual analytics cost: " + estimate.level + (estimate.modes.length ? " with prompt mode modifier" : "");
        if (!suggestions.length) {
          box.hidden = true;
          box.innerHTML = "";
          return;
        }
        box.innerHTML = "";
        suggestions.forEach(function (suggestion, index) {
          suggestion = suggestion || {};
          suggestion.tone = suggestion.tone || promptSuggestionTone(suggestion.prompt || question, index);
          suggestion.estimate = suggestion.estimate || estimateManualPromptCredits(state.metadata, suggestion.prompt || question);
          var item = document.createElement("button");
          item.type = "button";
          item.className = "rbe-sheet-suggest-item";
          item.style.background = suggestion.tone.background;
          item.style.color = suggestion.tone.color;
          item.style.setProperty("--rbe-suggest-border", suggestion.tone.border);
          item.style.setProperty("--rbe-suggest-accent", suggestion.tone.accent);
          item.style.setProperty("--rbe-suggest-hover", suggestion.tone.hover);
          item.setAttribute("aria-label", "Use suggested analytics prompt");
          var title = document.createElement("span");
          title.className = "rbe-sheet-suggest-title";
          title.textContent = suggestion.title + " | ~" + suggestion.estimate.credits + " credits";
          var prompt = document.createElement("span");
          prompt.className = "rbe-sheet-suggest-prompt";
          prompt.textContent = suggestion.prompt;
          var meta = document.createElement("span");
          meta.className = "rbe-sheet-suggest-meta";
          meta.textContent = suggestion.reason;
          item.appendChild(title);
          item.appendChild(prompt);
          item.appendChild(meta);
          item.addEventListener("click", function () {
            input.value = suggestion.prompt;
            if (/self[\s-]*repaired/i.test(String(suggestion.title || ""))) {
              state.acceptedManualSuggestions = state.acceptedManualSuggestions || [];
              state.acceptedManualSuggestions.push({
                original_prompt: question,
                suggested_prompt: suggestion.prompt,
                suppress_reuse: true
              });
              if (state.acceptedManualSuggestions.length > 20) state.acceptedManualSuggestions = state.acceptedManualSuggestions.slice(-20);
            }
            state.acceptedManualSuggestion = {
              original_prompt: question,
              suggested_prompt: suggestion.prompt,
              suggestion_title: suggestion.title,
              suggestion_reason: suggestion.reason,
              estimated_credits: suggestion.estimate.credits,
              deterministic_route: suggestion.deterministicRoute || "",
              execution_plan: suggestion.executionPlan || null,
              validation: suggestion.validation || { ok: true, issues: [] },
              result_snapshot: suggestion.resultSnapshot || null
            };
            updateManualButtonState();
            renderManualPromptAssist(true);
            logQualityEvent({
              feature: "sheet_analyzer",
              status: "suggestion_accepted",
              route: suggestion.deterministicRoute || "manual_prompt_suggestion",
              prompt: question,
              contract: manualIntentContract(state.metadata, suggestion.prompt),
              validation: suggestion.validation || { ok: true, issues: [] },
              metadata: {
                suggested_prompt: suggestion.prompt,
                suggestion_title: suggestion.title,
                suggestion_reason: suggestion.reason,
                estimated_credits: suggestion.estimate.credits,
                deterministic_route: suggestion.deterministicRoute || "",
                execution_plan: suggestion.executionPlan || null,
                result_snapshot: suggestion.resultSnapshot || null,
                suggestion_quality_gate: "deterministic_route_validated_before_display",
                row_count: (rowsForAnalytics(state.metadata) || []).length || (state.metadata && state.metadata.rowCount) || 0
              }
            });
            input.focus();
            try {
              input.setSelectionRange(input.value.length, input.value.length);
            } catch (error) {}
          });
          box.appendChild(item);
        });
        box.hidden = false;
      }
      function scheduleManualPromptAssist(forceHide) {
        if (state.manualPromptAssistTimer) {
          global.clearTimeout(state.manualPromptAssistTimer);
          state.manualPromptAssistTimer = null;
        }
        var input = wrap.querySelector(".rbe-sheet-manual-input");
        var question = input ? String(input.value || "").trim() : "";
        if (forceHide || !question || !state.metadata) {
          setManualPromptAssistBusy(false);
          renderManualPromptAssist(forceHide);
          return;
        }
        setManualPromptAssistBusy(true);
        state.manualPromptAssistTimer = global.setTimeout(function () {
          state.manualPromptAssistTimer = null;
          renderManualPromptAssist(false);
          setManualPromptAssistBusy(false);
        }, 90);
      }
      async function runManualAnalytics() {
        var input = wrap.querySelector(".rbe-sheet-manual-input");
        var button = wrap.querySelector(".rbe-sheet-manual-btn");
        var question = String(input.value || "").trim();
        if (!question) return;
        if (!state.metadata) {
          notify("Run sheet analysis before asking manual analytics questions.", "warning");
          return;
        }
        if (state.manualPromptAssistTimer) {
          global.clearTimeout(state.manualPromptAssistTimer);
          state.manualPromptAssistTimer = null;
        }
        setManualPromptAssistBusy(false);
        button.disabled = true;
        input.disabled = true;
        renderManualPromptAssist(true);
        status("Starting manual analytics", false);
        appendAnalysisLine("Starting manual analytics for this prompt.");
        await yieldToBrowser();
        await yieldToBrowser();
        var modeContract = promptModeContract(question);
        var derivedSpec = derivedMetricSpec(state.metadata || {}, question);
        var analyticsRows = rowsForAnalytics(state.metadata);
        var acceptedSuggestion = state.acceptedManualSuggestion && state.acceptedManualSuggestion.suggested_prompt === question ? state.acceptedManualSuggestion : null;
        var acceptedBankContract = acceptedSuggestion && acceptedSuggestion.execution_plan && acceptedSuggestion.execution_plan.contract
          ? normalizeStoredIntentContract(state.metadata, question, acceptedSuggestion.execution_plan.contract)
          : null;
        var promptBankContract = acceptedBankContract || findPromptBankContract(question, true);
        var manualDecision = canonicalManualAnalyticsDecision(state.metadata, question, promptBankContract);
        var intentContract = manualDecision.contract;
        var plan = { level: manualDecision.billing.level, credits: manualDecision.billing.credits, action: manualDecision.action };
        if (manualNarrativeInterpretationIntent(question)) {
          status("Running narrative interpretation (" + plan.credits + " credits)", false);
          var narrativeStartedAt = Date.now();
          var narrativeMetadata = metadataForAi(state.metadata, false);
          var narrativeGrounding = manualNarrativeGroundingContext(state.metadata);
          var narrativeQuestion = manualNarrativePrompt(state.metadata, question);
          try {
            var narrativePayload = await followupInChunks({
              file_id: state.fileId,
              metadata: narrativeMetadata,
              rows: [],
              full_rows: [],
              row_count: analyticsRows.length || (state.metadata && state.metadata.rowCount) || 0,
              analysis: Object.assign({}, state.analysis || {}, { grounded_context: narrativeGrounding, hybrid_rule: narrativeGrounding.hybrid_rule }),
              question: narrativeQuestion,
              original_question: question,
              analysis_level: "interpretation",
              credits_required: plan.credits,
              intent_contract: Object.assign({}, intentContract, { intentType: "narrative_interpretation", requiresChart: false, requiresTable: false }),
              billing_mode: "single_manual_question",
              create_blocks: true,
              force_ai_blocks: true
            }, function (chunk) {
              appendAnalysisLine("Narrative interpretation is reviewing sheet context pass " + chunk.index + " of " + chunk.total + ".");
              status("Running narrative interpretation pass " + chunk.index + " of " + chunk.total, false);
            });
            followupCount += 1;
            var narrativeBlocks = narrativeInterpretationBlocks(state.metadata, question, narrativePayload, followupCount);
            var narrativeValidation = validateNarrativeInterpretationBlocks(narrativeBlocks, state.metadata);
            if (!narrativeValidation.ok) {
              narrativeBlocks = [{
                type: "toggle",
                data: {
                  title: "Question " + followupCount + ": " + question,
                  open: true,
                  blocks: narrativeFallbackAnswerBlocks(state.metadata, question, narrativeValidation.issues)
                }
              }];
              narrativeValidation = validateNarrativeInterpretationBlocks(narrativeBlocks, state.metadata);
              if (!narrativeValidation.ok) {
                if (usageEventId(narrativePayload)) await failManualAiUsage(editor, narrativePayload, question, narrativeValidation.issues.join(" "));
                input.disabled = false;
                updateManualButtonState();
                renderManualPromptAssist(true);
                clearBusy();
                notify("Narrative interpretation could not create a valid answer, so no credits were deducted.", "warning");
                input.focus();
                return;
              }
            }
            if (usageEventId(narrativePayload)) await completeManualAiUsage(editor, narrativePayload, state.metadata, question, plan.credits, narrativeBlocks, narrativeStartedAt);
            await yieldToBrowser();
            appendNestedBlocks(narrativeBlocks);
            state.nestedBlocks = nestedCanvas && editor.serializeBlock ? Array.prototype.slice.call(nestedCanvas.children).map(editor.serializeBlock.bind(editor)) : (state.nestedBlocks || []).concat(narrativeBlocks);
            input.value = "";
            state.acceptedManualSuggestion = null;
            input.disabled = false;
            updateManualButtonState();
            renderManualPromptAssist(true);
            clearBusy();
            notify("Narrative interpretation added successfully.", "success");
            syncState();
            editor.changed(true);
            input.focus();
            return;
          } catch (error) {
            input.disabled = false;
            updateManualButtonState();
            renderManualPromptAssist(true);
            clearBusy();
            showError(error && error.message || "Narrative interpretation failed.");
            input.focus();
            return;
          }
        }
        if (manualPromptNeedsDatasetGuidance(question) || !manualPromptLooksDatasetRelated(state.metadata, question)) {
          followupCount += 1;
          var clarificationBlocks = [{
            type: "toggle",
            data: {
              title: "Question " + followupCount + ": " + question,
              open: true,
              blocks: manualClarificationBlocks(state.metadata, question)
            }
          }];
          await yieldToBrowser();
          appendNestedBlocks(clarificationBlocks);
          state.nestedBlocks = nestedCanvas && editor.serializeBlock ? Array.prototype.slice.call(nestedCanvas.children).map(editor.serializeBlock.bind(editor)) : (state.nestedBlocks || []).concat(clarificationBlocks);
          input.value = "";
          state.acceptedManualSuggestion = null;
          input.disabled = false;
          updateManualButtonState();
          renderManualPromptAssist(true);
          clearBusy();
          notify("Please ask a specific analytics question from this sheet.", "warning");
          syncState();
          editor.changed(true);
          input.focus();
          return;
        }
        status("Running " + plan.level + " manual analytics (" + plan.credits + " credits)", false);
        var fastPipeline = await runDeterministicAnalyticsPipelineAsync(state.metadata, question, function (message) {
          status(message + " (" + (analyticsRows.length || (state.metadata && state.metadata.rowCount) || 0) + " rows)", false);
        }, intentContract);
        if (fastPipeline && fastPipeline.executionResult && fastPipeline.executionResult.validation && fastPipeline.executionResult.validation.plan && !fastPipeline.executionResult.validation.plan.ok) {
          followupCount += 1;
          var planFailureBlocks = executionFailureBlocks(question, fastPipeline.executionResult.validation.issues, followupCount);
          logQualityEvent({
            feature: "sheet_analyzer",
            status: "failed_closed",
            route: "manual_analytics_execution_plan",
            prompt: question,
            contract: intentContract,
            validation: fastPipeline.executionResult.validation,
            metadata: {
              attempts: 1,
              row_count: analyticsRows.length || (state.metadata && state.metadata.rowCount) || 0,
              failed_closed: true,
              prompt_modes: modeContract.modes,
              deterministic_fast_path: true,
              from_validated_suggestion: !!acceptedSuggestion,
              accepted_suggestion: acceptedSuggestion,
              prompt_bank_contract: !!promptBankContract
            }
          });
          await yieldToBrowser();
          appendNestedBlocks(planFailureBlocks);
          state.nestedBlocks = nestedCanvas && editor.serializeBlock ? Array.prototype.slice.call(nestedCanvas.children).map(editor.serializeBlock.bind(editor)) : (state.nestedBlocks || []).concat(planFailureBlocks);
          input.value = "";
          state.acceptedManualSuggestion = null;
          input.disabled = false;
          updateManualButtonState();
          renderManualPromptAssist(true);
          clearBusy();
          notify((fastPipeline.executionResult.validation.issues || ["Manual analytics needs a clearer metric or grouping before it can run."]).join(" "), "warning");
          syncState();
          editor.changed(true);
          input.focus();
          return;
        }
        if (fastPipeline && fastPipeline.blocks && fastPipeline.blocks.length && fastPipeline.validation && fastPipeline.validation.ok) {
          followupCount += 1;
          var fastBlocks = [{
            type: "toggle",
            data: {
              title: "Question " + followupCount + ": " + question,
              open: true,
              blocks: fastPipeline.blocks
            }
          }];
          logQualityEvent({
            feature: "sheet_analyzer",
            status: "passed",
            route: "manual_analytics_fast_path",
            prompt: question,
            contract: intentContract,
            validation: fastPipeline.validation,
            metadata: {
              attempts: 1,
              row_count: analyticsRows.length || (state.metadata && state.metadata.rowCount) || 0,
              failed_closed: false,
              prompt_modes: modeContract.modes,
              deterministic_fast_path: true,
              from_validated_suggestion: !!acceptedSuggestion,
              accepted_suggestion: acceptedSuggestion,
              prompt_bank_contract: !!promptBankContract
            }
          });
          logQualityLineage({
            feature: "sheet_analyzer",
            route: "manual_analytics_fast_path",
            status: "passed",
            prompt: question,
            intent: intentContract,
            execution_plan: fastPipeline.plan,
            rules_executed: fastPipeline.validation.rulesRun || [],
            rules_failed: fastPipeline.validation.blockingRules || [],
            repairs: { attempts: 1, failed_closed: false, deterministic: true, fast_path: true },
            rows_used: analyticsRows.length || (state.metadata && state.metadata.rowCount) || 0,
            result: fastPipeline.snapshot,
            validation: fastPipeline.validation,
            suggestion: acceptedSuggestion
          });
          var manualUsageStartedAt = Date.now();
          try {
            var manualUsage = await startManualAnalyticsUsage(editor, state.metadata, question, plan.credits);
            await completeManualAnalyticsUsage(editor, manualUsage, state.metadata, question, plan.credits, fastBlocks, manualUsageStartedAt);
          } catch (billingError) {
            input.disabled = false;
            updateManualButtonState();
            renderManualPromptAssist(true);
            clearBusy();
            showError(billingError && billingError.message || "Manual analytics credit deduction failed.");
            input.focus();
            return;
          }
          await yieldToBrowser();
          appendNestedBlocks(fastBlocks);
          await saveManualPromptContract(question, intentContract, plan.credits, fastPipeline.validation);
          state.nestedBlocks = nestedCanvas && editor.serializeBlock ? Array.prototype.slice.call(nestedCanvas.children).map(editor.serializeBlock.bind(editor)) : (state.nestedBlocks || []).concat(fastBlocks);
          input.value = "";
          state.acceptedManualSuggestion = null;
          input.disabled = false;
          updateManualButtonState();
          renderManualPromptAssist(true);
          clearBusy();
          notify("Manual analytics answer added successfully.", "success");
          syncState();
          editor.changed(true);
          input.focus();
          return;
        }
        var compactManualMetadata = metadataForAi(state.metadata, plan.level === "deep");
        var aiManualStartedAt = Date.now();
        fetchQualityRules("sheet_analyzer", question).then(function (qualityRules) {
        var instructionText = [qualityRulesText(qualityRules), promptModeInstructions(modeContract)].filter(Boolean).join("\n\n");
        return followupInChunks({
          file_id: state.fileId,
          metadata: compactManualMetadata,
          rows: [],
          full_rows: [],
          row_count: analyticsRows.length || (state.metadata && state.metadata.rowCount) || 0,
          analysis: state.analysis || {},
          question: question,
          analysis_level: plan.level,
          credits_required: plan.credits,
          derived_metric: derivedSpec ? derivedSpec.name : "",
          derived_formula: derivedSpec ? derivedSpec.formula : "",
          intent_contract: intentContract,
          prompt_modes: modeContract,
          quality_rules: qualityRules,
          quality_instructions: instructionText,
          billing_mode: "single_manual_question",
          create_blocks: true
        }, function (chunk) {
          appendAnalysisLine("Manual analytics pass " + chunk.index + " of " + chunk.total + " is checking rows " + chunk.startRow + "-" + chunk.endRow + " (" + chunk.rowCount + " rows).");
          status("Running manual analytics pass " + chunk.index + " of " + chunk.total, false);
        }).then(function resolveValidatedPayload(payload) {
          followupCount += 1;
          function buildBlocks(answerQuestion, sourcePayload) {
            return followupBlocks(state.metadata, answerQuestion, sourcePayload, followupCount);
          }
          function runRepair(answerQuestion, sourcePayload, attempt) {
            var blocks = buildBlocks(question, sourcePayload);
            var validation = validateManualAnalyticsBlocks(state.metadata, question, blocks, intentContract);
            var deterministicPipeline = runDeterministicAnalyticsPipeline(state.metadata, question, intentContract);
            var registryValidation = deterministicPipeline.validation || { ok: true, issues: [] };
            if (!registryValidation.ok) {
              validation.issues = (validation.issues || []).concat(registryValidation.issues || []);
              validation.ok = false;
            }
            var deterministic = deterministicPipeline.blocks || [];
            if (deterministic.length && !(sourcePayload && sourcePayload.force_ai_blocks)) {
              if (!validation.ok) {
                validation.issues = validation.issues.filter(function (issue) {
                  return !/sample metadata|sample-limited|no table or chart block was created/i.test(issue);
                });
                validation.ok = !validation.issues.length;
              }
              if (validation.ok) {
                return Promise.resolve({ blocks: blocks, validation: validation, registryValidation: registryValidation, executionPlan: deterministicPipeline.plan, resultSnapshot: deterministicPipeline.snapshot, attempts: attempt, deterministic: true, usagePayload: sourcePayload });
              }
              appendAnalysisLine("Deterministic analytics route produced blocks but validation still found: " + validation.issues.join(" "));
              return Promise.resolve({ blocks: validationFailureBlocks(question, validation.issues, followupCount), validation: validation, registryValidation: registryValidation, executionPlan: deterministicPipeline.plan, resultSnapshot: deterministicPipeline.snapshot, attempts: attempt, failedClosed: true, usagePayload: sourcePayload });
            }
            if (validation.ok) return Promise.resolve({ blocks: blocks, validation: validation, registryValidation: registryValidation, executionPlan: deterministicPipeline.plan, resultSnapshot: deterministicPipeline.snapshot, attempts: attempt, usagePayload: sourcePayload });
            if (attempt >= 3) {
              return Promise.resolve({ blocks: validationFailureBlocks(question, validation.issues, followupCount), validation: validation, registryValidation: registryValidation, executionPlan: deterministicPipeline.plan, resultSnapshot: deterministicPipeline.snapshot, attempts: attempt, failedClosed: true, usagePayload: sourcePayload });
            }
            var repairQuestion = manualRepairQuestion(question, validation, attempt + 1, intentContract, qualityRules);
            appendAnalysisLine("AI review found a mismatch. Repair attempt " + (attempt + 1) + " is correcting: " + validation.issues.join(" "));
            status("Repairing manual analytics answer " + (attempt + 1) + " of 3", false);
            return followupInChunks({
              file_id: state.fileId,
              metadata: compactManualMetadata,
              rows: [],
              full_rows: [],
              row_count: analyticsRows.length || (state.metadata && state.metadata.rowCount) || 0,
              analysis: state.analysis || {},
              question: repairQuestion,
              original_question: question,
              analysis_level: plan.level,
              credits_required: plan.credits,
              derived_metric: derivedSpec ? derivedSpec.name : "",
              derived_formula: derivedSpec ? derivedSpec.formula : "",
              intent_contract: intentContract,
              prompt_modes: modeContract,
              quality_rules: qualityRules,
              quality_instructions: instructionText,
              billing_mode: "single_manual_question",
              create_blocks: true,
              force_ai_blocks: true,
              ai_validation_loop: true,
              ai_validation_attempt: attempt + 1,
              validation_issues: validation.issues
            }, function (chunk) {
              appendAnalysisLine("Manual analytics repair pass " + chunk.index + " of " + chunk.total + " is checking rows " + chunk.startRow + "-" + chunk.endRow + " (" + chunk.rowCount + " rows).");
              status("Repairing manual analytics pass " + chunk.index + " of " + chunk.total, false);
            }).then(function (repairPayload) {
              repairPayload = Object.assign({}, repairPayload || {}, { force_ai_blocks: true });
              return runRepair(repairQuestion, repairPayload, attempt + 1);
            });
          }
          return runRepair(question, payload, 1);
        }).then(function (result) {
          logQualityEvent({
            feature: "sheet_analyzer",
            status: result && result.validation && result.validation.ok ? "passed" : "failed",
            route: "manual_analytics",
            prompt: question,
            contract: intentContract,
            validation: result && result.validation || {},
            metadata: {
              attempts: result && result.attempts || 1,
              row_count: analyticsRows.length || (state.metadata && state.metadata.rowCount) || 0,
              failed_closed: !!(result && result.failedClosed),
              prompt_modes: modeContract.modes,
              from_validated_suggestion: !!acceptedSuggestion,
              accepted_suggestion: acceptedSuggestion,
              prompt_bank_contract: !!promptBankContract,
              avoid_suggestion: !!(acceptedSuggestion && !(result && result.validation && result.validation.ok)),
              suggestion_learning_signal: acceptedSuggestion && !(result && result.validation && result.validation.ok) ? "validated_suggestion_failed_final_contract" : ""
            }
          });
          logQualityLineage({
            feature: "sheet_analyzer",
            route: "manual_analytics",
            status: result && result.validation && result.validation.ok ? "passed" : "failed",
            prompt: question,
            intent: intentContract,
            execution_plan: result && result.executionPlan || buildAnalyticsExecutionPlanWithContract(state.metadata, question, intentContract),
            rules_executed: result && result.registryValidation && result.registryValidation.rulesRun || [],
            rules_failed: result && result.registryValidation && result.registryValidation.blockingRules || [],
            repairs: {
              attempts: result && result.attempts || 1,
              failed_closed: !!(result && result.failedClosed),
              deterministic: !!(result && result.deterministic)
            },
            rows_used: analyticsRows.length || (state.metadata && state.metadata.rowCount) || 0,
            result: result && result.resultSnapshot || extractResultSnapshot(result && result.blocks || []),
            validation: result && result.validation || {},
            suggestion: acceptedSuggestion
          });
          var blocks = result.blocks || [];
          if (result && result.failedClosed || isValidationFailureAnswer(blocks)) {
            if (usageEventId(result && result.usagePayload)) {
              failManualAiUsage(editor, result.usagePayload, question, "Manual analytics validation failed.").catch(function (billingError) {
                if (global.console && console.warn) console.warn("Manual analytics failure update failed:", billingError);
              });
            }
            state.acceptedManualSuggestion = null;
            updateManualButtonState();
            renderManualPromptAssist(true);
            clearBusy();
            notify("Manual analytics could not create a validated answer, so no answer was inserted.", "warning");
            return null;
          }
          if (blocks[0] && blocks[0].type === "toggle" && blocks[0].data) {
            blocks[0].data.title = "Question " + followupCount + ": " + question;
          }
          if (usageEventId(result && result.usagePayload)) {
            return completeManualAiUsage(editor, result.usagePayload, state.metadata, question, plan.credits, blocks, aiManualStartedAt).then(function () {
              saveManualPromptContract(question, Object.assign({}, intentContract, { source: "manual_ai_success_contract" }), plan.credits, result && result.validation);
              return blocks;
            });
          }
          return saveManualPromptContract(question, intentContract, plan.credits, result && result.validation).then(function () {
            return blocks;
          });
        }).then(function (blocks) {
          if (!blocks || !blocks.length) return;
          appendNestedBlocks(blocks);
          state.nestedBlocks = nestedCanvas && editor.serializeBlock ? Array.prototype.slice.call(nestedCanvas.children).map(editor.serializeBlock.bind(editor)) : (state.nestedBlocks || []).concat(blocks);
          input.value = "";
          state.acceptedManualSuggestion = null;
          updateManualButtonState();
          renderManualPromptAssist(true);
          clearBusy();
          notify("Manual analytics answer added. Requested " + plan.credits + " credits.", "success");
          syncState();
          editor.changed(true);
        }).catch(function (error) {
          logQualityEvent({
            feature: "sheet_analyzer",
            status: "failed",
            route: "manual_analytics_exception",
            prompt: question,
            contract: intentContract,
            validation: { ok: false, issues: [error && error.message || "Manual analytics failed."] },
            metadata: {
              row_count: analyticsRows.length || (state.metadata && state.metadata.rowCount) || 0,
              prompt_modes: modeContract.modes,
              failed_closed: true
            }
          });
          if (usageEventId(error && error.details)) {
            failManualAiUsage(editor, error.details, question, error && error.message || "Manual analytics failed.").catch(function () {});
          }
          showError((error && error.message ? error.message : "Manual analytics failed.") + " No validated answer was inserted.");
        }).finally(function () {
          input.disabled = false;
          updateManualButtonState();
          renderManualPromptAssist();
          input.focus();
        });
        });
      }
      wrap.querySelector(".rbe-sheet-manual-btn").addEventListener("click", runManualAnalytics);
      wrap.querySelector(".rbe-sheet-manual-input").addEventListener("input", function () {
        if (state.acceptedManualSuggestion && String(this.value || "").trim() !== state.acceptedManualSuggestion.suggested_prompt) {
          state.acceptedManualSuggestion = null;
        }
        updateManualButtonState();
        scheduleManualPromptAssist();
      });
      wrap.querySelector(".rbe-sheet-manual-input").addEventListener("keydown", function (event) {
        if (event.key === "Enter") {
          event.preventDefault();
          runManualAnalytics();
        } else if (event.key === "Escape") {
          renderManualPromptAssist(true);
        }
      });
      wrap.querySelector(".rbe-sheet-delete").addEventListener("click", function () {
        if (mainAnalysisRunning) return;
        if (!state.fileId) return;
        request("file_delete", { file_id: state.fileId }).then(function () {
          clearDraft(state);
          resetUploadState();
          syncState();
          notify("Uploaded sheet deleted.", "success");
          editor.changed(true);
        }).catch(function (error) {
          showError(error.message);
        });
      });
      refreshCost();
      renderMeta();
      renderResult();
      var staleRestore = !previewRender && !!(state.metadata && (state.status === "uploaded" || state.status === "analyzed" || state.analysis) && !hasUsableSheetRows(state.metadata));
      if (staleRestore) {
        setMainAnalysisRunning(false);
        wrap.querySelector(".rbe-sheet-analyze").disabled = true;
        showManualAnalytics(false);
        notify("Sheet row data is missing after refresh. Clear and upload again.", "warning");
        setTimeout(showStaleRestoreModal, 0);
      } else {
        showManualAnalytics(data.status === "analyzed" || !!state.analysis || (Array.isArray(data.nestedBlocks) && data.nestedBlocks.length > 1));
      }
      updateManualButtonState();
      syncState();
    },
    serialize: function (block) {
      var nestedCanvas = block.querySelector(".rbe-sheet-nested-canvas");
      var data = block.__rbeSheetState || {};
      var nestedBlocks = nestedCanvas && this.serializeBlock ? Array.prototype.slice.call(nestedCanvas.children).map(this.serializeBlock.bind(this)) : [];
      var previewSerialize = isSheetPreviewRender(this);
      if (!hasSheetState(data) && !(previewSerialize && hasNestedContent(nestedBlocks))) nestedBlocks = [];
      var hasOutput = hasSheetState(data) || (previewSerialize && hasNestedContent(nestedBlocks));
      var serialized = {
        draftId: data.draftId || null,
        status: hasOutput ? (data.status || (block.querySelector(".rbe-sheet-result:not([hidden])") || hasNestedContent(nestedBlocks) ? "analyzed" : "uploaded")) : "empty",
        metadata: data.metadata || null,
        fileId: data.fileId || null,
        analysis: data.analysis || null,
        blocks: data.blocks || [],
        nestedBlocks: nestedBlocks
      };
      if (hasSheetState(serialized)) writeDraft(serialized);
      return serialized;
    },
    toHTML: function (data, helpers) {
      var nested = data && Array.isArray(data.nestedBlocks) && helpers && typeof helpers.renderBlock === "function" ? data.nestedBlocks.map(helpers.renderBlock).join("") : "";
      return '<section class="rbe-output-sheet-analyzer rbe-output-sheet-analyzer-summary"><strong>AI Sheet Analyzer</strong><p>Spreadsheet analysis block.</p></section>' + nested;
    },
    toMarkdown: function (data, helpers) {
      var nested = data && Array.isArray(data.nestedBlocks) && helpers && typeof helpers.text === "function" ? "\n\n" + data.nestedBlocks.map(function (block) { return helpers.text(block.data && block.data.text || ""); }).filter(Boolean).join("\n\n") : "";
      return "[AI Sheet Analyzer]" + nested;
    }
  });
})(typeof window !== "undefined" ? window : this);
