@salesforce/afv-skills 1.59.0 → 1.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-architecture-analyze/SKILL.md +66 -17
  3. package/skills/agentforce-architecture-analyze/assets/cli/show_access_token.yaml +4 -7
  4. package/skills/agentforce-architecture-analyze/references/contract.json +1 -1
  5. package/skills/agentforce-architecture-analyze/scripts/emit_env.py +2 -2
  6. package/skills/agentforce-architecture-analyze/scripts/main.py +40 -44
  7. package/skills/agentforce-architecture-analyze/scripts/rest_client.py +11 -3
  8. package/skills/agentforce-architecture-analyze/scripts/sf_cli.py +77 -10
  9. package/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py +1 -0
  10. package/skills/agentforce-architecture-analyze/scripts/tests/test_flow_children_inflation.py +1 -0
  11. package/skills/agentforce-architecture-analyze/scripts/tests/test_main_pipeline.py +53 -2
  12. package/skills/agentforce-architecture-analyze/scripts/tests/test_resolve_creds.py +184 -36
  13. package/skills/agentforce-architecture-analyze/scripts/tests/test_rest_client.py +17 -0
  14. package/skills/agentforce-architecture-analyze/scripts/tests/test_sf_cli.py +165 -11
  15. package/skills/agentforce-architecture-analyze/scripts/tests/test_skill_md_pipeline_block.py +319 -0
  16. package/skills/agentforce-d360-analyze/SKILL.md +28 -10
  17. package/skills/agentforce-d360-analyze/scripts/dc.py +200 -50
  18. package/skills/agentforce-d360-analyze/scripts/discover_sessions.py +10 -6
  19. package/skills/agentforce-d360-analyze/scripts/fetch_dc.py +22 -6
  20. package/skills/agentforce-d360-analyze/scripts/resolve_session.py +21 -13
  21. package/skills/agentforce-d360-analyze/scripts/tests/test_dc_and_resolve_session.py +298 -76
  22. package/skills/agentforce-d360-analyze/scripts/tests/test_default_target_org.py +432 -0
  23. package/skills/agentforce-d360-analyze/scripts/tests/test_discover_sessions.py +1 -1
  24. package/skills/agentforce-d360-analyze/scripts/tests/test_resolve_session_main.py +9 -3
  25. package/skills/agentforce-d360-analyze/scripts/tests/test_skill_md_prefix_block.py +176 -0
  26. package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +17 -27
  27. package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +7 -17
  28. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +47 -63
  29. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +10 -13
  30. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -46
  31. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +4 -21
  32. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +58 -82
  33. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +16 -315
  34. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +9 -24
  35. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -44
  36. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +11 -63
  37. package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +2 -1
  38. package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +2 -1
  39. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +2 -1
  40. package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +2 -1
  41. package/skills/data360-schema-get/SKILL.md +3 -3
  42. package/skills/design-systems-slds-validate/references/report-format.md +1 -1
  43. package/skills/dx-org-manage/SKILL.md +1 -0
  44. package/skills/dx-org-shape-manage/SKILL.md +1 -0
  45. package/skills/dx-org-snapshot-manage/SKILL.md +172 -0
  46. package/skills/dx-org-snapshot-manage/examples/create_error_output.json +16 -0
  47. package/skills/dx-org-snapshot-manage/examples/create_success_output.json +19 -0
  48. package/skills/dx-org-snapshot-manage/examples/delete_output.json +9 -0
  49. package/skills/dx-org-snapshot-manage/examples/get_output.json +19 -0
  50. package/skills/dx-org-snapshot-manage/examples/list_output.json +36 -0
  51. package/skills/dx-org-snapshot-manage/references/cli_flags.md +179 -0
  52. package/skills/dx-org-snapshot-manage/references/snapshot_usage.md +55 -0
  53. package/skills/dx-org-trial-expiration-check/SKILL.md +1 -1
  54. package/skills/dx-org-trial-expiration-check/scripts/check_expiration.sh +2 -2
  55. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +7 -5
  56. package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +11 -11
  57. package/skills/experience-content-media-image-generate/SKILL.md +83 -0
  58. package/skills/experience-content-media-image-generate/scripts/download-image.sh +84 -0
  59. package/skills/external-diagram-mermaid-generate/SKILL.md +12 -12
  60. package/skills/field-service-data-capture-migrate/references/component-mapping.md +1 -1
  61. package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +1 -2
  62. package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +1 -0
  63. package/skills/field-service-data-capture-migrate/references/transformer-rules.md +1 -1
  64. package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +12 -1
  65. package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +4 -1
  66. package/skills/field-service-setup-orchestrator-get/SKILL.md +0 -1
  67. package/skills/insurance-brokerage-agency-billing-configure/SKILL.md +233 -0
  68. package/skills/insurance-brokerage-agency-billing-configure/references/accounting-setup.md +117 -0
  69. package/skills/insurance-brokerage-agency-billing-configure/references/billing-rules-config.md +237 -0
  70. package/skills/insurance-brokerage-agency-billing-configure/references/billing-settings.md +271 -0
  71. package/skills/insurance-brokerage-agency-billing-configure/references/demo-policy-config.md +138 -0
  72. package/skills/insurance-brokerage-agency-billing-configure/references/org-defaults.md +64 -0
  73. package/skills/insurance-brokerage-agency-billing-configure/references/permissions-config.md +226 -0
  74. package/skills/insurance-brokerage-agency-billing-configure/references/prerequisites.md +142 -0
  75. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +3 -2
  76. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +3 -3
  77. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +68 -45
  78. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +17 -18
  79. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +16 -3
  80. package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +16 -13
  81. package/skills/marketing-message-test/SKILL.md +194 -0
  82. package/skills/marketing-message-test/references/api-contract.md +333 -0
  83. package/skills/omnistudio-callable-apex-generate/SKILL.md +3 -3
  84. package/skills/omnistudio-datamapper-generate/SKILL.md +6 -6
  85. package/skills/omnistudio-datapacks-deploy/SKILL.md +11 -11
  86. package/skills/omnistudio-dependencies-analyze/SKILL.md +5 -5
  87. package/skills/omnistudio-epc-catalog-generate/SKILL.md +6 -6
  88. package/skills/omnistudio-flexcard-generate/SKILL.md +6 -6
  89. package/skills/omnistudio-integration-procedure-generate/SKILL.md +6 -6
  90. package/skills/omnistudio-omniscript-generate/SKILL.md +6 -5
  91. package/skills/platform-apex-logs-debug/SKILL.md +1 -0
  92. package/skills/platform-metadata-deploy/SKILL.md +1 -0
  93. package/skills/platform-models-api-configure/SKILL.md +5 -5
  94. package/skills/platform-soql-query/SKILL.md +1 -0
  95. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +17 -6
  96. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +107 -192
  97. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +6 -6
  98. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +49 -20
  99. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +4 -4
  100. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +224 -290
  101. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/workflow.md +295 -0
  102. package/skills/service-itsm-incident-priority-configure/SKILL.md +1 -1
  103. package/skills/service-itsm-slack-itservice-configure/SKILL.md +2 -1
  104. package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +2 -2
  105. package/skills/service-omni-channel-setup-coordinate/SKILL.md +20 -13
  106. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +5 -55
  107. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +0 -38
  108. package/skills/service-omni-command-center-analyze/SKILL.md +2 -3
  109. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
  110. package/skills/tableau-next-asset-create/README.md +65 -0
  111. package/skills/tableau-next-asset-create/SKILL.md +409 -0
  112. package/skills/tableau-next-asset-create/references/admin-users.md +149 -0
  113. package/skills/tableau-next-asset-create/references/ai-readiness.md +355 -0
  114. package/skills/tableau-next-asset-create/references/alerts.md +111 -0
  115. package/skills/tableau-next-asset-create/references/build-workflow.md +819 -0
  116. package/skills/tableau-next-asset-create/references/dashboard-authoring.md +302 -0
  117. package/skills/tableau-next-asset-create/references/dashboard-design-principles.md +352 -0
  118. package/skills/tableau-next-asset-create/references/data-transform-gotchas.md +210 -0
  119. package/skills/tableau-next-asset-create/references/data-understanding.md +387 -0
  120. package/skills/tableau-next-asset-create/references/deep-reference-index.md +74 -0
  121. package/skills/tableau-next-asset-create/references/destructive-operations.md +62 -0
  122. package/skills/tableau-next-asset-create/references/edit-dashboard.md +273 -0
  123. package/skills/tableau-next-asset-create/references/edit-visualization.md +529 -0
  124. package/skills/tableau-next-asset-create/references/empty-source-handling.md +104 -0
  125. package/skills/tableau-next-asset-create/references/ingest-and-metric-gotchas.md +638 -0
  126. package/skills/tableau-next-asset-create/references/insight-bundle.md +69 -0
  127. package/skills/tableau-next-asset-create/references/large-flat-file-handling.md +331 -0
  128. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_envelope.json +54 -0
  129. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_shared-widget-fields.json +122 -0
  130. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/add_page.json +29 -0
  131. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/delete_page.json +27 -0
  132. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/discard_dashboard.json +11 -0
  133. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget.json +30 -0
  134. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget_to_page.json +30 -0
  135. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/place_widget_on_page.json +30 -0
  136. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_dashboard.json +16 -0
  137. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_page.json +26 -0
  138. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/rename_page.json +28 -0
  139. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/save_dashboard.json +18 -0
  140. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/undo.json +11 -0
  141. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_button_widget.json +69 -0
  142. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_container_widget.json +88 -0
  143. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_filter_widget.json +146 -0
  144. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_metric_widget.json +78 -0
  145. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_navigation_widget.json +80 -0
  146. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_parameter_widget.json +160 -0
  147. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_text_widget.json +103 -0
  148. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_visualization_widget.json +68 -0
  149. package/skills/tableau-next-asset-create/references/sdm-tool-reference.md +721 -0
  150. package/skills/tableau-next-asset-create/references/semantic-query-and-enrichment.md +355 -0
  151. package/skills/tableau-next-asset-create/references/shared-gates.md +322 -0
  152. package/skills/tableau-next-asset-create/references/sharing-and-promotion.md +306 -0
  153. package/skills/tableau-next-asset-create/references/tasks/ai-readiness-audit.md +51 -0
  154. package/skills/tableau-next-asset-create/references/tasks/analyze-data.md +48 -0
  155. package/skills/tableau-next-asset-create/references/tasks/build-dashboard.md +66 -0
  156. package/skills/tableau-next-asset-create/references/tasks/build-end-to-end.md +73 -0
  157. package/skills/tableau-next-asset-create/references/tasks/create-data-transform.md +51 -0
  158. package/skills/tableau-next-asset-create/references/tasks/create-viz.md +58 -0
  159. package/skills/tableau-next-asset-create/references/tasks/edit-dashboard.md +80 -0
  160. package/skills/tableau-next-asset-create/references/tasks/edit-sdm.md +106 -0
  161. package/skills/tableau-next-asset-create/references/tasks/edit-viz.md +40 -0
  162. package/skills/tableau-next-asset-create/references/tasks/enrich-model.md +68 -0
  163. package/skills/tableau-next-asset-create/references/tasks/ingest-flat-file.md +68 -0
  164. package/skills/tableau-next-asset-create/references/tasks/manage-alert.md +35 -0
  165. package/skills/tableau-next-asset-create/references/tasks/profile-dataset.md +47 -0
  166. package/skills/tableau-next-asset-create/references/tasks/promote-or-reuse.md +46 -0
  167. package/skills/tableau-next-asset-create/references/tasks/provision-user.md +39 -0
  168. package/skills/tableau-next-asset-create/references/tasks/query-model.md +92 -0
  169. package/skills/tableau-next-asset-create/references/tasks/review-verified-questions.md +42 -0
  170. package/skills/tableau-next-asset-create/references/tasks/share-asset.md +37 -0
  171. package/skills/tableau-next-asset-create/references/verified-questions.md +125 -0
  172. package/skills/tableau-next-asset-create/references/viz-authoring.md +992 -0
  173. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +0 -102
@@ -0,0 +1,992 @@
1
+ # Visualization Authoring — Tool Guide & Best Practices
2
+
3
+ Load this for **Step 10** (create visualizations). Covers tool selection,
4
+ chart-type best practices mapped to the available MCP tools, field binding rules,
5
+ aggregation handling, naming conventions, and verified payloads.
6
+
7
+ **Best-practice principles** for dashboard design live in
8
+ `references/dashboard-design-principles.md`; this file maps the chart-level
9
+ mechanics to what the tools actually support. For chart types not currently
10
+ supported by the tools, see §9.
11
+
12
+ > **⚠ Single-tool surface (verified 2026-07-09):** The MCP server exposes one
13
+ > viz creation tool: `create_visualization`. The formerly-documented specialized
14
+ > tools (`create_timeseries_viz`, `create_comparative_viz`,
15
+ > `create_compositional_viz`, `create_relationship_viz`) are **not available**.
16
+ > All chart types are built via `create_visualization` with the appropriate
17
+ > `marks.panes.type` and `visualSpecification`. Payloads in §11 are verified
18
+ > against the live server.
19
+
20
+ ---
21
+
22
+ ## Contents
23
+
24
+ - 1. Data Presence Gate (prerequisite — hard stop)
25
+ - 2. Chart Type Parameters
26
+ - 3. Chart Type Decision Matrix
27
+ - 4. Naming Conventions
28
+ - 5. Field Selection Priorities
29
+ - 6. Color Encoding and Breakdown
30
+ - 7. Aggregation Handling
31
+ - 8. Year-over-Year Period Overlay Pattern
32
+ - 9. Unsupported Chart Types — Fallbacks
33
+ - §10 — Filter Security
34
+ - 10. Field Reference Shape (`create_visualization`)
35
+ - 11. Verified Payloads (`create_visualization`)
36
+ - 12. `create_visualization` Encoding Reference
37
+ - 13. Quality Checklist
38
+ - 14. Filters — WHERE vs. HAVING
39
+ - 15. Forecasts (Timeseries Only)
40
+ - 16. Calculated-Measure Level → Function Pairing
41
+ - 17. Reference Line Knob
42
+ - 18. Common Errors
43
+
44
+ ---
45
+
46
+ ## 1. Data Presence Gate (prerequisite — hard stop)
47
+
48
+ **Never create a visualization on a source with zero rows.**
49
+
50
+ This is Rule 1 from SKILL.md: field-richness is NOT data-presence. Before
51
+ calling any viz creation tool, confirm that Step 9 (`run_semantic_query`)
52
+ returned real rows. If the query returned empty → **STOP**. Do not call
53
+ `create_visualization`. Communicate clearly that the source is empty and offer
54
+ alternatives (different data source, different metric).
55
+
56
+ ---
57
+
58
+ ## 2. Chart Type Parameters
59
+
60
+ **One tool handles all chart types:** `create_visualization`. Control the
61
+ chart type via `marks.panes.type` and `visualSpecification.layout`:
62
+
63
+ | Chart Type / Pattern | `marks.panes.type` | `layout` | Notes |
64
+ |---|---|---|---|
65
+ | **Line / Time Series** | `Line` | `Vizql` | Date on columns with `DateTruncMonth`; measure on rows |
66
+ | **Multi-series Line** | `Line` | `Vizql` | As above + Color encoding on breakdown dimension |
67
+ | **Bar (comparative, sorted)** | `Bar` | `Vizql` | Dim on columns, measure on rows; set `sortIntent: "MeasureDescending"` |
68
+ | **Stacked Bar** | `Bar` | `Vizql` | As above + `stack: {isAutomatic: false, isStacked: true}` + Color on second dim |
69
+ | **Side-by-Side Bar** | `Bar` | `Vizql` | Dim on columns; `MeasureValues` on rows + `MeasureNames` on Color; see `measureValues` |
70
+ | **Donut / Pie** | `Donut` | `Radial` | Measure on **`Angle` encoding** (NOT `rows`); dimension on Color; use when dim has < 5 unique values |
71
+ | **Scatter / Correlation** | `Circle` | `Vizql` | X-measure on columns, Y-measure on rows; point dimension on Color |
72
+ | **Heatmap** | `Square` | `Vizql` | Two dims on rows/columns; measure on Color encoding |
73
+ | **Table** | `Text` | `Table` | Dims in `groups`; measures in `rows`; `marks.panes.type` is ignored |
74
+
75
+ > **Donut gotcha:** Set `layout: "Radial"`, empty `rows`/`columns`. The measure
76
+ > goes on the `Angle` encoding, the dimension on `Color` — NOT on `rows` or
77
+ > `columns`. If you put the measure on `rows` the call will fail or render
78
+ > incorrectly. See §11 for the verified payload.
79
+
80
+ ---
81
+
82
+ ## 3. Chart Type Decision Matrix
83
+
84
+ | Business Question | Data Pattern | Chart Type |
85
+ |---|---|---|
86
+ | "How are we trending over time?" | 1 Date + 1 Measure | Line |
87
+ | "How do trends compare across categories?" | 1 Date + 1 Measure + 1 Dimension | Multi-series Line |
88
+ | "Which categories perform best?" | 1 Dimension + 1 Measure | Bar (sorted descending) |
89
+ | "What's our composition?" (< 5 categories) | 1 Dimension (< 5 vals) + 1 Measure | Donut |
90
+ | "What's our composition?" (≥ 5 categories) | 1 Dimension (≥ 5 vals) + 1 Measure | Bar — more readable beyond 4 slices |
91
+ | "What's the breakdown by two dimensions?" | 2 Dimensions + 1 Measure | Stacked Bar |
92
+ | "How do multiple measures compare?" | 1 Dimension + 2+ Measures | Side-by-Side Bar |
93
+ | "Where are the performance hotspots?" | 2 Dimensions + 1 Measure | Heatmap |
94
+ | "What correlates with success?" | 2 Continuous Measures + Grouping | Scatter |
95
+ | "What are the detailed rankings?" | Multiple Dimensions + Measures | Table |
96
+ | "How do 2024 vs 2023 compare month by month?" | Period + Measure + Year | Line (year-over-year — see §8) |
97
+ | "Predict next quarter revenue" | 1 Continuous date + 1 Measure | Line + `visualSpecification.forecasts` (HoltWinters / RidgeRegression) |
98
+
99
+ **Priority rules:**
100
+ 1. Use the decision matrix first: data pattern → tool
101
+ 2. When multiple patterns fit, prefer the tool that requires fewer manual
102
+ encoding overrides
103
+ 3. Prefer diverse chart types over defaulting to bar for every request
104
+
105
+ **Analysis-intent → chart (do this before picking marks):**
106
+
107
+ | User intent (example) | Analysis type | Tool guidance | §reference |
108
+ |---|---|---|---|
109
+ | "revenue over time" | trend | Line (date on columns) | §11 Time Series |
110
+ | "top categories by sales" | comparison | Bar (dim on columns, sorted) | §11 Bar |
111
+ | "market-share breakdown" | part-to-whole | Donut (< 5 vals) or Bar (≥ 5) | §11 Donut |
112
+ | "discount vs profit relationship" | correlation | Scatter (2 measures, 1 color dim) | §11 Scatter |
113
+ | "predict next quarter revenue" | forecast | Line + `forecasts` map (timeseries only) | §11 Time Series (forecast) |
114
+
115
+ **Forecast** is supported on timeseries **Line** charts via
116
+ `visualSpecification.forecasts` (API 262+): continuous `DateTrunc*` date on the
117
+ axis, measure to forecast, `HoltWinters` or `RidgeRegression`, horizon under
118
+ `to.forecastInterval`. Non-Line / non-timeseries forecasts fail with
119
+ `INVALID_INPUT`.
120
+
121
+ ---
122
+
123
+ ## 4. Naming Conventions
124
+
125
+ Chart titles are **user-facing**. Every chart must answer a business question
126
+ and have a clear, non-technical title (use SDM labels over technical names;
127
+ strip suffixes).
128
+
129
+ **Rules:**
130
+ 1. Title must be ≥ 3 words describing what the chart answers.
131
+ 2. Strip technical suffixes from display labels: `_Clc`, `_mtc`, `_MTC`, `_CLC`.
132
+ 3. Never expose raw field API names or suffixed names (`Region4`, `Amount7`).
133
+ 4. Make titles actionable: "Sales Performance by Region" not "Sales by Region".
134
+ 5. The `name` (apiName) returned by the tool must be meaningful if you control
135
+ it (e.g. specify `title`); the generated name (`monthly_sales_trend_…`) is
136
+ acceptable for dashboard placement but label the chart clearly.
137
+
138
+ ✅ **Good:** `"Sales Performance by Region"`, `"Monthly Revenue Trend"`,
139
+ `"Discount vs Profit by Product"`
140
+
141
+ ❌ **Bad:** `"Viz_1"`, `"Account_Industry_2"`, `"Pipeline_Generation_Clc_Trend"`,
142
+ `"Chart"`, `"Region4 by Amount7"`
143
+
144
+ ---
145
+
146
+ ## 5. Field Selection Priorities
147
+
148
+ **Always select meaningful dimensions — never ID fields.** CLC fields rank
149
+ highest (calculated, business-ready); ID fields are excluded (thousands of
150
+ unique values destroy chart readability).
151
+
152
+ Priority order (highest to lowest):
153
+ 1. **CLC fields** (`*_clc`, `*_Clc`) — calculated, business-ready
154
+ 2. Industry, Type, Stage, Status, Region, Segment, Category, Group, Class, Tier,
155
+ Level, Phase
156
+ 3. Country, State, Territory, Department, Division, Account, Opportunity,
157
+ Product, Service
158
+ 4. Name fields (Account_Name, Product_Name, etc.)
159
+ 5. **Avoid:** Description, Comment, Note, Detail, Text (generic catch-all fields)
160
+ 6. **Never:** ID fields (`_id`, `_ids` suffix) — thousands of unique values;
161
+ destroys chart readability
162
+
163
+ **Cardinality thresholds:**
164
+ - ≤ 4 unique values → Donut acceptable
165
+ - 5–50 values → Bar chart preferred dimension
166
+ - 50+ values → Apply a Top-N limit, or group to a higher-level category
167
+ (e.g., use `Category` instead of `Product_Name` with 200 values)
168
+
169
+ ---
170
+
171
+ ## 6. Color Encoding and Breakdown
172
+
173
+ **Color encodes meaning, not decoration.** When a second dimension is available,
174
+ always add it as a `breakdown` or Color encoding — this creates multi-series or
175
+ stacked visuals automatically.
176
+
177
+ | Chart type | How to add a color / second dimension |
178
+ |---|---|
179
+ | **All chart types** | `{ "type": "Color", "fieldKey": "<dim_field_key>" }` in `marks.panes.encodings` |
180
+ | **YoY line** | Color fieldKey = date field with `function: "DatePartYear"`, `displayCategory: "Discrete"` |
181
+ | **Stacked bar** | Color fieldKey = second dimension; also set `stack: { isAutomatic: false, isStacked: true }` |
182
+ | **Donut** | Color fieldKey = the category dimension (same field as Angle slice) |
183
+
184
+ **Rule:** When 2+ dimensions are available for a bar or line chart, add the
185
+ second dimension as a breakdown/Color encoding. Color should reference a
186
+ specific field, not be a static palette applied without data binding.
187
+
188
+ **Color vs. Detail by cardinality.** Use `Color` for series with ≤10 distinct
189
+ values, `Detail` for higher cardinality.
190
+
191
+ **Default stacking:** Bar → stacked, others → not stacked. Override with
192
+ `marks.panes.stack: { isAutomatic: false, isStacked: <bool> }`.
193
+
194
+ **Cross-chart color coordination** (same category = same color across multiple
195
+ charts) is a dashboard-level concern, not a per-chart rule.
196
+
197
+ ---
198
+
199
+ ## 7. Aggregation Handling
200
+
201
+ ### UserAgg for CLC / Calculated Fields
202
+
203
+ CLC fields (e.g., `Win_Rate_Clc`, `Avg_Deal_Size_Clc`) contain expressions like
204
+ `SUM([Field]) / COUNT([Field])` that aggregate at their own level.
205
+ **Never override UserAgg with Sum, Average, or any other function.**
206
+
207
+ ```jsonc
208
+ "fields": {
209
+ "F_clc": { "fieldName": "Win_Rate_Clc", "function": "UserAgg", "displayCategory": "Continuous" }
210
+ // objectName omitted — calc measures never carry objectName
211
+ }
212
+ ```
213
+
214
+ Overriding with `Sum` or `Average` causes double-aggregation → mathematically
215
+ incorrect results (e.g., `SUM(Average_Deal_Size)` is meaningless).
216
+
217
+ ### Aggregation Selection by Analytical Question
218
+
219
+ | Question | Aggregation |
220
+ |---|---|
221
+ | "Total revenue / sum" | `Sum` |
222
+ | "Average deal size / mean" | `Avg` |
223
+ | "Number of records / count" | `Count` |
224
+ | "Unique customer count" | `CountD` |
225
+ | "Calculated rate / ratio (CLC field)" | `UserAgg` |
226
+ | "Highest / lowest value" | `Max` / `Min` |
227
+
228
+ ### Pre-Aggregated Field Handling
229
+
230
+ Fields identified as pre-aggregated (marked `[AGG]` or using `usr:` derivation
231
+ in the semantic model) must not be re-aggregated. Set `function: "UserAgg"` on
232
+ the field's entry in the `fields` map and pass the field directly — the
233
+ aggregation is already embedded in the field expression.
234
+
235
+ ---
236
+
237
+ ## 8. Year-over-Year Period Overlay Pattern
238
+
239
+ **Always use period overlay, NOT a continuous date timeline, for YoY comparison.**
240
+
241
+ | Approach | X-axis | Color | Result |
242
+ |---|---|---|---|
243
+ | ✅ Period overlay (correct) | Month/Quarter (`DatePartMonth`) | Year dimension | Multiple aligned lines; seasonal patterns obvious |
244
+ | ❌ Continuous timeline (wrong) | Full date range (Jan 2023–Dec 2024) | — | Hard to compare same period across years |
245
+
246
+ **Why period overlay works:** Each year's January lands on the same X position
247
+ (month 1), making the same-period comparison direct and vertical rather than
248
+ requiring horizontal eye movement across a 2-year timeline.
249
+
250
+ **Implementation with `create_visualization`:**
251
+
252
+ Two date fields from the same date column — one with `DatePartMonth` (X-axis)
253
+ and one with `DatePartYear` (Color). No calculated dimension is needed; the
254
+ date part functions split the field into the two analytical roles inline:
255
+
256
+ ```jsonc
257
+ {
258
+ "workspaceId": "1Dy…", "workspaceName": "my_workspace",
259
+ "semanticModelName": "My_SDM", "semanticModelLabel": "My SDM",
260
+ "fields": {
261
+ "F_month": {
262
+ "objectName": "Orders", "fieldName": "Order_Date",
263
+ "function": "DatePartMonth", "displayCategory": "Discrete",
264
+ "label": "Month"
265
+ },
266
+ "F_year": {
267
+ "objectName": "Orders", "fieldName": "Order_Date",
268
+ "function": "DatePartYear", "displayCategory": "Discrete",
269
+ "label": "Year"
270
+ },
271
+ "F_meas": {
272
+ "objectName": "Orders", "fieldName": "Sales",
273
+ "function": "Sum", "displayCategory": "Continuous",
274
+ "numberFormat": "Currency"
275
+ }
276
+ },
277
+ "visualSpecification": {
278
+ "rows": ["F_meas"],
279
+ "columns": ["F_month"],
280
+ "layout": "Vizql",
281
+ "marks": {
282
+ "panes": {
283
+ "type": "Line",
284
+ "encodings": [{ "fieldKey": "F_year", "type": "Color" }]
285
+ }
286
+ }
287
+ },
288
+ "title": "Monthly Sales: Year-over-Year Comparison"
289
+ }
290
+ ```
291
+
292
+ The `DatePartYear` Color encoding creates one line per year, all sharing the
293
+ same 12-month X-axis.
294
+
295
+ ---
296
+
297
+ ## 9. Unsupported Chart Types — Fallbacks
298
+
299
+ The following types appear in the universal visualization best practices guide
300
+ but are **not supported by the current MCP tools**. Use the recommended
301
+ fallback and communicate the limitation to the user:
302
+
303
+ | Unsupported Type | Reason | Recommended Fallback |
304
+ |---|---|---|
305
+ | **Funnel Chart** | No funnel tool | `create_visualization (Bar marks)`, sorted by stage order; communicate each stage's count |
306
+ | **Dot Matrix** | No dot matrix tool | `create_visualization (Circle marks)` for 2 measures, or `create_visualization (Bar marks)` for ranking |
307
+ | **Map (Point / Geographic)** | No map tool; lat/lon not routed | `create_visualization (Bar marks)` by Region or Territory dimension |
308
+ | **Flow / Sankey** | No flow tool | `create_visualization (Bar marks)` showing source → destination volumes as two separate charts |
309
+ | **Nightingale Rose** | Radial layout supports Donut marks only | `create_visualization (Bar marks)` (horizontal bar) |
310
+ | **Spoke Bars (Radial)** | Not available in current tools | `create_visualization (Bar marks)` |
311
+ | **Radial Heatmap** | Not available | `create_visualization` Square-mark heatmap (2D grid) |
312
+
313
+ **When the user explicitly requests an unsupported type:** say so clearly —
314
+ "This chart type is not currently available. I'll use [fallback] instead, which
315
+ answers the same question with [benefit]." Do not silently substitute without
316
+ explaining.
317
+
318
+ **Note:** Funnel, Dot Matrix, Map, Flow/Sankey, Nightingale Rose, and Spoke Bar
319
+ chart types cannot be verified via MCP tool call trace with the current toolset.
320
+ Validate the fallback communication behavior for these instead.
321
+
322
+ ---
323
+
324
+ ## §10 — Filter Security
325
+
326
+ ### Hidden Filters Are Not a Security Control
327
+
328
+ When a user asks to hide a filter, lock a filter to a specific value, or restrict
329
+ which data users see by hiding a filter dimension, issue this warning before
330
+ proceeding:
331
+
332
+ > **Warning:** Hidden filters in dashboards are not a security control. Any user
333
+ > with direct API or URL access to the underlying data source can bypass a hidden
334
+ > filter and retrieve the full dataset. Do not rely on hidden filters to enforce
335
+ > data access restrictions or row-level security.
336
+ >
337
+ > **Recommendation:** For genuine data access restrictions, implement row-level
338
+ > security at the data source level (e.g., data source permissions,
339
+ > entitlement-based filtering in the semantic model). Hidden filters are
340
+ > appropriate only for UI/UX convenience (e.g., pre-selecting a default value
341
+ > the user can still change).
342
+
343
+ This warning applies when the user's intent is access restriction — not when they
344
+ simply want a pre-set default filter value that the end user can still override.
345
+ This applies regardless of whether the user says the restriction is "just for
346
+ convenience."
347
+
348
+ ---
349
+
350
+ ## 10. Field Reference Shape (`create_visualization`)
351
+
352
+ `create_visualization` uses a **`fields` map + `visualSpecification`** format —
353
+ NOT the `fieldRef` / `measureFunction` shape from older specialized tools.
354
+
355
+ ```jsonc
356
+ "fields": {
357
+ "<fieldKey>": {
358
+ "objectName": "<SDM data-object apiName>", // OMIT for model-level calc measures
359
+ "fieldName": "<field apiName on that object>",
360
+ "displayCategory": "Discrete | Continuous",
361
+ "function": "<aggregation or date function>", // optional
362
+ "level": "Row | AggregateFunction | TableCalc | Lod", // required when calc + function
363
+ "label": "<display label override>", // optional
364
+ "numberFormat": "Currency | Percent | Abbreviated | Precise", // optional, measures only
365
+ "referenceLine": "Average | Sum | Min | Max | Target", // optional, measures only — see §17
366
+ "referenceLineValue": 1000000, // required iff referenceLine=Target
367
+ "colorScale": "Diverging" // optional, Color-encoded measure — see §11 Heatmap
368
+ }
369
+ }
370
+ ```
371
+
372
+ **Server hydrates the rest — do NOT send these unless overriding:** `type:
373
+ "Field"` (auto-filled) and the field's inferred `role` (Measure when any
374
+ aggregation `function` is set, else Dimension); and at the spec level
375
+ `marks.headers` (default Text mark), `marks.fields` (one per continuous-axis
376
+ fieldKey on rows/columns), `legends` (auto-derived from Color encodings),
377
+ `referenceLines` (`{}`), `measureValues` (`[]`), `style` (full default
378
+ scaffold), `view` (empty), and `interactions` (`[]`).
379
+
380
+ **Critical rules:**
381
+ - `workspaceId` — workspace Id (e.g. `0Za...`). Get from `list_workspaces`
382
+ (`id` field). `workspaceName` — workspace API name (no spaces). Get from
383
+ `list_workspaces` (`name` field). **Do NOT pass the label-with-spaces.**
384
+ - `objectName` is the **SDM data-object apiName** (e.g., `Orders`, `Products`),
385
+ NOT the underlying DLO/DMO table name (`orders__dll`, `Orders__dlm`).
386
+ - `fieldName` is the field's apiName on that SDM object — use the **suffixed**
387
+ apiName for bulk-imported fields (e.g., `Category3` not `Category`; see
388
+ `shared-gates.md` G3 / `sdm-tool-reference.md` for the suffix read-back rule).
389
+ - **Calculated measures: omit `objectName`** entirely. Including `objectName`
390
+ for a calculated measure causes an `UNKNOWN_EXCEPTION`. When supplying
391
+ `function` on a calc, also set `level` from
392
+ `list_semantic_model_calculated_measures` (`UserAgg` only for
393
+ `AggregateFunction` / `TableCalc`).
394
+ - `displayCategory: "Continuous"` → measures and `DateTrunc*` date functions.
395
+ - `displayCategory: "Discrete"` → plain dimensions and `DatePart*` date functions.
396
+ - FieldKeys you define in `fields` must **exactly match** the strings used in
397
+ `visualSpecification.rows`, `columns`, and encoding `fieldKey` values
398
+ (these are the generate-viz field pointers).
399
+ - The tool returns the saved visualization's identifying triple (`id`, `label`,
400
+ `name`). **Capture `id` and `name`** for `edit_dashboard`'s
401
+ `upsert_visualization_widget`.
402
+ To inspect the full hydrated body (style, legends, marks.fields), GET the
403
+ visualization by id with `get_visualization`.
404
+
405
+ **`function` values:**
406
+ - Aggregations: `Sum`, `Avg`, `Median`, `Count`, `CountD`, `Min`, `Max`,
407
+ `UserAgg` (`UserAgg` only for model-level calc measures at
408
+ `AggregateFunction` / `TableCalc`)
409
+ - Continuous date trunc: `DateTruncYear`, `DateTruncQuarter`, `DateTruncMonth`,
410
+ `DateTruncWeek`, `DateTruncDay` (plus `FiscalDateTrunc*` variants)
411
+ - Discrete date part: `DatePartYear`, `DatePartQuarter`, `DatePartMonth`,
412
+ `DatePartWeek`, `DatePartDay`
413
+ - Fiscal-calendar variants (anchored to the org's fiscal year, no `Day`
414
+ granularity): `FiscalDateTruncYear/Quarter/Month/Week` (Continuous),
415
+ `FiscalDatePartYear/Quarter/Month/Week` (Discrete) — use only when the
416
+ request says "fiscal" explicitly; "this quarter" ≠ "this fiscal quarter".
417
+ - Plain dimensions: omit `function`
418
+
419
+ ---
420
+
421
+ ## 11. Verified Payloads (`create_visualization`)
422
+
423
+ All calls share: `workspaceId`, `workspaceName`, `semanticModelName`, `semanticModelLabel`, `label` (the viz display title).
424
+
425
+ ### Time Series (Line chart)
426
+
427
+ ```jsonc
428
+ {
429
+ "workspaceId": "1Dy…", "workspaceName": "my_workspace",
430
+ "semanticModelName": "My_SDM", "semanticModelLabel": "My SDM",
431
+ "label": "Monthly Sales Trend",
432
+ "fields": {
433
+ "F_date": {
434
+ "objectName": "Orders", "fieldName": "Order_Date",
435
+ "function": "DateTruncMonth", "displayCategory": "Continuous"
436
+ },
437
+ "F_meas": {
438
+ "objectName": "Orders", "fieldName": "Sales",
439
+ "function": "Sum", "displayCategory": "Continuous",
440
+ "numberFormat": "Currency"
441
+ }
442
+ },
443
+ "visualSpecification": {
444
+ "rows": ["F_meas"],
445
+ "columns": ["F_date"],
446
+ "layout": "Vizql",
447
+ "marks": { "panes": { "type": "Line" } }
448
+ }
449
+ }
450
+ ```
451
+
452
+ With multi-series breakdown (Color = second dimension):
453
+ ```jsonc
454
+ {
455
+ "fields": {
456
+ "F_date": { "objectName": "Orders", "fieldName": "Order_Date", "function": "DateTruncMonth", "displayCategory": "Continuous" },
457
+ "F_meas": { "objectName": "Orders", "fieldName": "Sales", "function": "Sum", "displayCategory": "Continuous" },
458
+ "F_dim": { "objectName": "Orders", "fieldName": "Region", "displayCategory": "Discrete" }
459
+ },
460
+ "visualSpecification": {
461
+ "rows": ["F_meas"], "columns": ["F_date"], "layout": "Vizql",
462
+ "marks": {
463
+ "panes": {
464
+ "type": "Line",
465
+ "encodings": [{ "fieldKey": "F_dim", "type": "Color" }]
466
+ }
467
+ }
468
+ },
469
+ "label": "Monthly Sales Trend by Region"
470
+ }
471
+ ```
472
+
473
+ With year-over-year period overlay:
474
+ ```jsonc
475
+ {
476
+ "fields": {
477
+ "F_month": { "objectName": "Orders", "fieldName": "Order_Date", "function": "DatePartMonth", "displayCategory": "Discrete", "label": "Month" },
478
+ "F_year": { "objectName": "Orders", "fieldName": "Order_Date", "function": "DatePartYear", "displayCategory": "Discrete", "label": "Year" },
479
+ "F_meas": { "objectName": "Orders", "fieldName": "Sales", "function": "Sum", "displayCategory": "Continuous", "numberFormat": "Currency" }
480
+ },
481
+ "visualSpecification": {
482
+ "rows": ["F_meas"], "columns": ["F_month"], "layout": "Vizql",
483
+ "marks": {
484
+ "panes": {
485
+ "type": "Line",
486
+ "encodings": [{ "fieldKey": "F_year", "type": "Color" }]
487
+ }
488
+ }
489
+ },
490
+ "label": "Monthly Sales: Year-over-Year Comparison"
491
+ }
492
+ ```
493
+
494
+ With forecast (HoltWinters, 6-month horizon) — fieldKeys are pointers into
495
+ `fields`; both `dateDim.fieldKey` and `measureFieldKey` must match:
496
+ ```jsonc
497
+ {
498
+ "label": "Sales Forecast — Next 6 Months",
499
+ "fields": {
500
+ "F_date": {
501
+ "objectName": "Orders", "fieldName": "Order_Date",
502
+ "function": "DateTruncMonth", "displayCategory": "Continuous"
503
+ },
504
+ "F_meas": {
505
+ "objectName": "Orders", "fieldName": "Sales",
506
+ "function": "Sum", "displayCategory": "Continuous",
507
+ "numberFormat": "Currency"
508
+ }
509
+ },
510
+ "visualSpecification": {
511
+ "rows": ["F_meas"],
512
+ "columns": ["F_date"],
513
+ "layout": "Vizql",
514
+ "marks": { "panes": { "type": "Line" } },
515
+ "forecasts": {
516
+ "FC1": {
517
+ "forecastModel": "HoltWinters",
518
+ "dimensionFields": [
519
+ { "dateDim": { "fieldKey": "F_date", "granularity": "InferFromData" } }
520
+ ],
521
+ "measureFieldKey": "F_meas",
522
+ "from": { "predefinedDate": "LatestData" },
523
+ "to": { "forecastInterval": { "value": 6, "timeUnit": "Months" } },
524
+ "includeBounds": true,
525
+ "confidenceLevel": 95
526
+ }
527
+ }
528
+ }
529
+ }
530
+ ```
531
+
532
+ ---
533
+
534
+ ### Bar / Comparative (sorted descending)
535
+
536
+ ```jsonc
537
+ {
538
+ "label": "Top Products by Revenue",
539
+ "fields": {
540
+ "F_dim": { "objectName": "Products", "fieldName": "Product_Name", "displayCategory": "Discrete" },
541
+ "F_meas": { "objectName": "Orders", "fieldName": "Sales", "function": "Sum", "displayCategory": "Continuous", "numberFormat": "Currency" }
542
+ },
543
+ "visualSpecification": {
544
+ "rows": ["F_meas"], "columns": ["F_dim"], "layout": "Vizql",
545
+ "marks": { "panes": { "type": "Bar" } }
546
+ },
547
+ "sortIntent": "MeasureDescending"
548
+ }
549
+ ```
550
+
551
+ With Color on a second dimension (auto-adds color encoding):
552
+ ```jsonc
553
+ {
554
+ "label": "Revenue by Region and Category",
555
+ "fields": {
556
+ "F_dim1": { "objectName": "Orders", "fieldName": "Region", "displayCategory": "Discrete" },
557
+ "F_dim2": { "objectName": "Products", "fieldName": "Category3", "displayCategory": "Discrete" },
558
+ "F_meas": { "objectName": "Orders", "fieldName": "Sales", "function": "Sum", "displayCategory": "Continuous" }
559
+ },
560
+ "visualSpecification": {
561
+ "rows": ["F_meas"], "columns": ["F_dim1"], "layout": "Vizql",
562
+ "marks": {
563
+ "panes": {
564
+ "type": "Bar",
565
+ "encodings": [{ "fieldKey": "F_dim2", "type": "Color" }]
566
+ }
567
+ }
568
+ },
569
+ "sortIntent": "MeasureDescending"
570
+ }
571
+ ```
572
+
573
+ With a CLC calculated measure (omit `objectName`):
574
+ ```jsonc
575
+ {
576
+ "label": "Win Rate by Stage",
577
+ "fields": {
578
+ "F_clc": { "fieldName": "Win_Rate_Clc", "function": "UserAgg", "displayCategory": "Continuous" },
579
+ "F_dim": { "objectName": "Opportunities", "fieldName": "Stage8", "displayCategory": "Discrete" }
580
+ },
581
+ "visualSpecification": {
582
+ "rows": ["F_clc"], "columns": ["F_dim"], "layout": "Vizql",
583
+ "marks": { "panes": { "type": "Bar" } }
584
+ },
585
+ "sortIntent": "MeasureDescending"
586
+ }
587
+ ```
588
+
589
+ ---
590
+
591
+ ### Stacked Bar (2 dimensions + 1 measure)
592
+
593
+ ```jsonc
594
+ {
595
+ "label": "Revenue by Region and Category",
596
+ "fields": {
597
+ "F_dim1": { "objectName": "Orders", "fieldName": "Region", "displayCategory": "Discrete" },
598
+ "F_dim2": { "objectName": "Products", "fieldName": "Category3", "displayCategory": "Discrete" },
599
+ "F_meas": { "objectName": "Orders", "fieldName": "Sales", "function": "Sum", "displayCategory": "Continuous" }
600
+ },
601
+ "visualSpecification": {
602
+ "rows": ["F_meas"], "columns": ["F_dim1"], "layout": "Vizql",
603
+ "marks": {
604
+ "panes": {
605
+ "type": "Bar",
606
+ "encodings": [{ "fieldKey": "F_dim2", "type": "Color" }],
607
+ "stack": { "isAutomatic": false, "isStacked": true }
608
+ }
609
+ }
610
+ },
611
+ "sortIntent": "MeasureDescending"
612
+ }
613
+ ```
614
+
615
+ ---
616
+
617
+ ### Side-by-Side Bar (1 dimension + 2+ measures)
618
+
619
+ Uses `MeasureNames` / `MeasureValues` synthetic fields. Put `MeasureValues` on
620
+ rows, `MeasureNames` on Color. List the real measure fieldKeys in `measureValues`.
621
+
622
+ > **Required:** `displayCategory` is **not optional** for synthetic fields.
623
+ > Omitting it causes an `UNKNOWN_EXCEPTION` (HTTP 500). Always set:
624
+ > - `MeasureNames` → `"displayCategory": "Discrete"`
625
+ > - `MeasureValues` → `"displayCategory": "Continuous"`
626
+
627
+ ```jsonc
628
+ {
629
+ "label": "Revenue and Units by Region",
630
+ "fields": {
631
+ "F_dim": { "objectName": "Orders", "fieldName": "Region", "displayCategory": "Discrete" },
632
+ "F_mv": { "type": "MeasureValues", "displayCategory": "Continuous" },
633
+ "F_mn": { "type": "MeasureNames", "displayCategory": "Discrete" },
634
+ "F_rev": { "objectName": "Orders", "fieldName": "Sales", "function": "Sum", "displayCategory": "Continuous", "numberFormat": "Currency" },
635
+ "F_units":{ "objectName": "Orders", "fieldName": "Quantity", "function": "Sum", "displayCategory": "Continuous" }
636
+ },
637
+ "visualSpecification": {
638
+ "rows": ["F_mv"], "columns": ["F_dim"], "layout": "Vizql",
639
+ "measureValues": ["F_rev", "F_units"],
640
+ "marks": {
641
+ "panes": {
642
+ "type": "Bar",
643
+ "encodings": [{ "fieldKey": "F_mn", "type": "Color" }]
644
+ }
645
+ }
646
+ }
647
+ }
648
+ ```
649
+
650
+ ---
651
+
652
+ ### Multi-Measure (direct shelf listing — separate adjacent axes)
653
+
654
+ The simpler multi-measure form: just list two (or more) measure fieldKeys on
655
+ one shelf — no `MeasureNames`/`MeasureValues` needed. This renders the measures
656
+ as **separate adjacent axes** (distinct from the shared-axis Side-by-Side Bar
657
+ above, which pivots them onto one axis via the measure-names construct). Works
658
+ for Bar and Line.
659
+
660
+ Multi-measure bar (both measures in `columns`):
661
+ ```jsonc
662
+ {
663
+ "label": "Sales and Profit by Category",
664
+ "fields": {
665
+ "F_dim": { "objectName": "Orders", "fieldName": "Category", "displayCategory": "Discrete" },
666
+ "F_sales": { "objectName": "Orders", "fieldName": "Sales", "function": "Sum", "displayCategory": "Continuous" },
667
+ "F_profit":{ "objectName": "Orders", "fieldName": "Profit", "function": "Sum", "displayCategory": "Continuous" }
668
+ },
669
+ "visualSpecification": {
670
+ "rows": ["F_dim"], "columns": ["F_sales", "F_profit"], "layout": "Vizql",
671
+ "marks": { "panes": { "type": "Bar" } }
672
+ }
673
+ }
674
+ ```
675
+
676
+ Multi-measure line (two lines sharing the date axis — both measures in `columns`):
677
+ ```jsonc
678
+ {
679
+ "label": "Sales and Profit over Time",
680
+ "fields": {
681
+ "F_date": { "objectName": "Orders", "fieldName": "Order_Date", "function": "DateTruncMonth", "displayCategory": "Continuous" },
682
+ "F_sales": { "objectName": "Orders", "fieldName": "Sales", "function": "Sum", "displayCategory": "Continuous" },
683
+ "F_profit":{ "objectName": "Orders", "fieldName": "Profit", "function": "Sum", "displayCategory": "Continuous" }
684
+ },
685
+ "visualSpecification": {
686
+ "rows": ["F_date"], "columns": ["F_sales", "F_profit"], "layout": "Vizql",
687
+ "marks": { "panes": { "type": "Line" } }
688
+ }
689
+ }
690
+ ```
691
+
692
+ Prefer this direct form for a quick side-by-side of a few measures; use the
693
+ `MeasureNames`/`MeasureValues` Side-by-Side Bar above when the measures should
694
+ share a single axis.
695
+
696
+ ---
697
+
698
+ ### Donut / Compositional (Radial layout)
699
+
700
+ > **Gotcha:** Set `layout: "Radial"`, empty `rows`/`columns`. Place the measure
701
+ > on the **`Angle` encoding**, NOT on `rows`. Putting a measure on `rows` with
702
+ > Radial layout causes the call to fail or render incorrectly. Verified working
703
+ > shape:
704
+
705
+ ```jsonc
706
+ {
707
+ "label": "Revenue Distribution by Region",
708
+ "fields": {
709
+ "F_meas": { "objectName": "Orders", "fieldName": "Sales", "function": "Sum", "displayCategory": "Continuous" },
710
+ "F_dim": { "objectName": "Orders", "fieldName": "Region", "displayCategory": "Discrete" }
711
+ },
712
+ "visualSpecification": {
713
+ "layout": "Radial",
714
+ "rows": [],
715
+ "columns": [],
716
+ "marks": {
717
+ "panes": {
718
+ "type": "Donut",
719
+ "encodings": [
720
+ { "fieldKey": "F_meas", "type": "Angle" },
721
+ { "fieldKey": "F_dim", "type": "Color" }
722
+ ]
723
+ }
724
+ }
725
+ }
726
+ }
727
+ ```
728
+
729
+ Use when dimension has **< 5 unique values**. For ≥ 5 values, use a Bar chart —
730
+ donut slices become unreadable beyond 4.
731
+
732
+ #### Part-to-whole / Donut — field-binding gotchas (`create_visualization`)
733
+
734
+ The Donut payload above is the current unified `create_visualization` shape
735
+ (`type: "Donut"`, `layout: "Radial"`). Older scripts may still name the retired
736
+ `create_compositional_viz` tool — that tool is **not** on the pilot MCP; always
737
+ use `create_visualization`.
738
+
739
+ **`objectName` / `level` for measures (matches generate-viz / IR-v2):**
740
+
741
+ - **Raw measure** (from `list_semantic_model_measures`): supply BOTH
742
+ `objectName` (SDM data-object apiName) and `fieldName`.
743
+ ```jsonc
744
+ "F_meas": { "objectName": "Orders", "fieldName": "Sales", "function": "Sum", "displayCategory": "Continuous" }
745
+ ```
746
+ - **Model-level calculated measure** (from
747
+ `list_semantic_model_calculated_measures`): supply ONLY `fieldName` — **omit
748
+ `objectName`**. Including `objectName` yields `UNKNOWN_EXCEPTION`. Copy the
749
+ calc's `level` from the list call whenever you set `function`:
750
+ - `AggregateFunction` / `TableCalc` → `function: "UserAgg"`
751
+ - `Row` / `Lod` → ordinary agg (`Avg` for Average, `CountD` for CountDistinct);
752
+ do **not** use `UserAgg`
753
+ ```jsonc
754
+ "F_meas": { "fieldName": "Profit_Margin", "level": "AggregateFunction", "function": "UserAgg", "displayCategory": "Continuous" }
755
+ ```
756
+
757
+ **FieldKeys are pointers:** every `rows` / `columns` / encoding `fieldKey` must
758
+ exactly match a key in the `fields` map — mismatches return
759
+ `INVALID_VISUALIZATION_METADATA`.
760
+
761
+ Validate dimensions/measures via `list_semantic_model_*` **before** calling
762
+ `create_visualization`. Donut is donut-only (no pie / treemap); for ≥ 5 unique
763
+ dimension values prefer §11 Bar.
764
+
765
+ ---
766
+
767
+ ### Scatter / Correlation (Circle marks)
768
+
769
+ ```jsonc
770
+ {
771
+ "label": "Discount vs Profit by Product",
772
+ "fields": {
773
+ "F_x": { "objectName": "Orders", "fieldName": "Discount", "function": "Avg", "displayCategory": "Continuous" },
774
+ "F_y": { "objectName": "Orders", "fieldName": "Profit", "function": "Sum", "displayCategory": "Continuous" },
775
+ "F_dim": { "objectName": "Products", "fieldName": "Product_Name", "displayCategory": "Discrete" }
776
+ },
777
+ "visualSpecification": {
778
+ "rows": ["F_y"], "columns": ["F_x"], "layout": "Vizql",
779
+ "marks": {
780
+ "panes": {
781
+ "type": "Circle",
782
+ "encodings": [{ "fieldKey": "F_dim", "type": "Color" }]
783
+ }
784
+ }
785
+ }
786
+ }
787
+ ```
788
+
789
+ ---
790
+
791
+ ### Heatmap (Square marks + Color(measure))
792
+
793
+ ```jsonc
794
+ {
795
+ "label": "Sales Performance by Region and Quarter",
796
+ "fields": {
797
+ "F_dim1": { "objectName": "Orders", "fieldName": "Region", "displayCategory": "Discrete" },
798
+ "F_dim2": { "objectName": "Orders", "fieldName": "Quarter3", "displayCategory": "Discrete" },
799
+ "F_meas": { "objectName": "Orders", "fieldName": "Sales", "function": "Sum", "displayCategory": "Continuous" }
800
+ },
801
+ "visualSpecification": {
802
+ "rows": ["F_dim1"], "columns": ["F_dim2"], "layout": "Vizql",
803
+ "marks": {
804
+ "panes": {
805
+ "type": "Square",
806
+ "encodings": [{ "fieldKey": "F_meas", "type": "Color" }]
807
+ }
808
+ }
809
+ }
810
+ }
811
+ ```
812
+
813
+ For diverging color scales (values above/below a threshold), add
814
+ `"colorScale": "Diverging"` to the measure's field binding — only for a measure
815
+ genuinely centered on zero/a target (profit, variance, YoY change); on
816
+ all-positive data a diverging scale misleads.
817
+
818
+ ---
819
+
820
+ ### Table
821
+
822
+ Use `groups` for the categorical grouping column(s) and `rows` for measures
823
+ (API version 262+). The `groups`/`rows` split is the recommended form — it
824
+ gives the grouping column merged-cell styling. Putting a dimension directly in
825
+ `rows` (alongside measures) also works and renders as a plain Text column;
826
+ prefer `groups` for the leftmost grouping dimension for cleaner output.
827
+ Every field renders as a discrete text column (`marks.panes.type` is ignored).
828
+
829
+ ```jsonc
830
+ {
831
+ "label": "Top Products by Revenue",
832
+ "fields": {
833
+ "F_dim": { "objectName": "Products", "fieldName": "Product_Name", "displayCategory": "Discrete" },
834
+ "F_meas": { "objectName": "Orders", "fieldName": "Sales", "function": "Sum", "displayCategory": "Continuous" }
835
+ },
836
+ "visualSpecification": {
837
+ "groups": ["F_dim"],
838
+ "rows": ["F_meas"],
839
+ "layout": "Table",
840
+ "marks": { "panes": { "type": "Text" } }
841
+ },
842
+ "sortIntent": "MeasureDescending"
843
+ }
844
+ ```
845
+
846
+ Sort the primary measure descending via `sortIntent: "MeasureDescending"` or by
847
+ pre-sorting the data query via `run_semantic_query` with `sort_orders: DESC`.
848
+ `sortIntent` is an input-only convenience field: the server does not persist it
849
+ verbatim — it is translated into an explicit `sortOrders` entry on the
850
+ dimension field (`{ byField: <measure>, order: "Descending", type: "Field" }`),
851
+ so `get_visualization` will show `sortOrders`, not `sortIntent`.
852
+
853
+ Top-level `sortIntent` values: `MeasureDescending` | `MeasureAscending` |
854
+ `DimensionAscending` | `DimensionDescending` | `None`. Use `DimensionAscending`
855
+ for lookup tables (A→Z is the point) and `None` to preserve data order — it
856
+ overrides the auto-sort described below.
857
+
858
+ ---
859
+
860
+ ## 12. `create_visualization` Encoding Reference
861
+
862
+ | Encoding type | Valid mark types | Purpose |
863
+ |---|---|---|
864
+ | `Color` | Bar, Line, Circle, Square, Donut | Category or measure heat gradient |
865
+ | `Label` | All | Text labels on marks |
866
+ | `Tooltip` | All | Hover detail |
867
+ | `Detail` | Bar, Line, Circle, Square | High-cardinality grouping without color |
868
+ | `Size` | Circle | Bubble size mapping (third measure) |
869
+ | `Range` | Line | Confidence interval band |
870
+ | `Angle` | Donut | Slice sizing (Radial layout) |
871
+
872
+ **`displayCategory` for field bindings:**
873
+ - `"Continuous"` → measures and `DateTrunc*` date functions
874
+ - `"Discrete"` → plain dimensions and `DatePart*` date functions
875
+
876
+ **Number formatting options:**
877
+ - `Currency` → `"$1.2M"` (assumes USD)
878
+ - `Percent` → `"45.2%"` (expects values stored as 0–1; do NOT use on a value
879
+ stored as `45.2`)
880
+ - `Abbreviated` → `"1.2K"` / `"1.2M"` (default for large numbers)
881
+ - `Precise` → `"1,234,567"` (full digits)
882
+
883
+ By default (no `numberFormat` set) the server applies `NumberShort` with K/M/B
884
+ units at **1 decimal place** — **0 decimals for `Count`/`CountD`** — so axes read
885
+ `$1.2M`-style, not `1234567.89`.
886
+
887
+ **Auto-sort only fires for the single-dim + single-measure case.** A bar chart
888
+ with exactly one categorical (non-date) dimension and one measure is sorted by
889
+ the measure descending automatically ("unsorted bars are nearly useless"). Date
890
+ axes stay chronological regardless. Charts with 2+ shelf dimensions or 2+
891
+ measures are left unsorted — the sort key is ambiguous — so use `sortIntent`
892
+ explicitly for those.
893
+
894
+ ---
895
+
896
+ ## 13. Quality Checklist
897
+
898
+ Before submitting any viz creation call:
899
+
900
+ - [ ] Step 9 confirmed real data (non-empty source) before calling `create_visualization`
901
+ - [ ] Title is ≥ 3 words, business-friendly, no raw API names or technical suffixes
902
+ - [ ] `fields.<key>.fieldName` uses the **suffixed** apiName from Step 5 (if bulk-imported)
903
+ - [ ] `objectName` is **omitted** for calculated measures
904
+ - [ ] `function: "UserAgg"` for CLC / calculated fields — not overridden
905
+ - [ ] Second dimension added as `breakdown` or Color encoding when available
906
+ - [ ] `MeasureNames` synthetic field has `displayCategory: "Discrete"` and `MeasureValues` has `displayCategory: "Continuous"` — both are required; omitting either causes HTTP 500
907
+ - [ ] Donut (Radial layout) only for dimension cardinality < 5; measure on `Angle` encoding, NOT `rows`
908
+ - [ ] Year-over-year uses `DatePartMonth` + Year breakdown, NOT continuous timeline
909
+ - [ ] Unsupported chart type → fallback communicated clearly to user
910
+ - [ ] Chart type matches the business question (decision matrix applied)
911
+
912
+ ---
913
+
914
+ ## 14. Filters — WHERE vs. HAVING
915
+
916
+ `create_visualization`'s optional `filters` array persists filter clauses at
917
+ create time (distinct from `edit_visualization`'s `addFilter` op —
918
+ `edit-visualization.md` — which mutates an already-saved viz). You don't
919
+ choose the bucket; it's derived from the target field's role:
920
+
921
+ - Filter on a **raw field** (no aggregation `function`) → rows/WHERE —
922
+ `Processing_Fee between 0 and 21`.
923
+ - Filter on an **aggregated measure** (field carries `Sum`/`Avg`/`Count`/…) →
924
+ aggregated groups/HAVING — `SUM(Refund_Amount) between 100 and 500`.
925
+
926
+ Shape: `{fieldKey, type: "Dimension"|"ValueRange"|"DateRange", ...}`.
927
+ Dimension filters take exactly one of `include`/`exclude` (never both/neither)
928
+ plus optional `includeNulls` (default false; ignored on range filters, which
929
+ always exclude nulls). `ValueRange`/`DateRange` need at least one of
930
+ `min`/`max` or `dateStart`/`dateEnd` — one-sided emits a GreaterThanOrEqualTo/
931
+ LessThanOrEqualTo, both emits Between (inclusive; `min` must be ≤ `max`) — and
932
+ only apply to `Continuous` fields; use a Dimension filter for a `Discrete`
933
+ (string) one. `DateRange` bounds must be real calendar dates (ISO 8601
934
+ `YYYY-MM-DD` or full timestamp) — a malformed value like `2025-99-99` is
935
+ rejected up front. Filters compose with `excludeNulls` (which adds its own
936
+ aggregated null filter) without clobbering each other.
937
+
938
+ **Sorted-dimension subtlety:** filtering the auto-sorted categorical
939
+ dimension of a bar chart keeps the auto-sort (filter runs on an internal
940
+ copy). Filtering the *measure* the chart sorts by drops the auto-sort — a
941
+ measure can't be both a sort key and a filter target.
942
+
943
+ ## 15. Forecasts (Timeseries Only)
944
+
945
+ `visualSpecification.forecasts` is a map of `forecastKey -> config`, valid
946
+ **only** on a Line chart with a continuous `DateTrunc*` dimension on the
947
+ timeseries axis — a non-timeseries chart (e.g. Bar) fails `INVALID_INPUT`.
948
+
949
+ Required: `forecastModel` (`HoltWinters` — seasonality/trend — or
950
+ `RidgeRegression` — linear/L2); `dimensionFields` (one entry, `dateDim.
951
+ fieldKey` matching a `Continuous` `DateTrunc*` field in `fields`,
952
+ `granularity: "InferFromData"` only); `measureFieldKey` (a measure field);
953
+ `from: {predefinedDate: "LatestData"}`; `to.forecastInterval` (`value` > 0 +
954
+ `timeUnit` ∈ Minutes/Hours/Days/Weeks/Months/Quarters/Years).
955
+
956
+ Optional: `includeBounds` (default true), `confidenceLevel` (90/95/99,
957
+ default 95), `ignoreLastPeriods` (default 0), `fillNullsWithZero` (default
958
+ false). A `dateDim`/`measureFieldKey` missing from `fields`, or of the wrong
959
+ type, fails `INVALID_INPUT` naming the offending forecastKey.
960
+
961
+ ## 16. Calculated-Measure Level → Function Pairing
962
+
963
+ When binding a model-level calculated measure (from
964
+ `list_semantic_model_calculated_measures`, omit `objectName` — §10), copy its
965
+ `level` into the field's `level` property whenever `function` is set, then
966
+ pick `function` by that level:
967
+
968
+ | `level` | `function` |
969
+ |---|---|
970
+ | `AggregateFunction` / `TableCalc` | `UserAgg` |
971
+ | `Row` / `Lod` (LOD) | ordinary aggregation matching `aggregationType` (`Average`→`Avg`, `CountDistinct`→`CountD`), or omit `function` when `aggregationType` is `Auto`/`None` |
972
+
973
+ A missing or mismatched level/function pair is rejected before the
974
+ visualization saves.
975
+
976
+ ## 17. Reference Line Knob
977
+
978
+ A per-measure-field knob (`fields.<key>.referenceLine`), distinct from
979
+ `edit_visualization`'s `addReferenceLine`/`editReferenceLine` operations
980
+ (`edit-visualization.md` §6) which target an already-saved viz. Values:
981
+ `Average`/`Sum`/`Min`/`Max` (computed line) or `Target` with a required
982
+ `referenceLineValue` (constant quota/threshold) — no `Median`. Keep to at
983
+ most one per chart.
984
+
985
+ ## 18. Common Errors
986
+
987
+ | Error | Root cause |
988
+ |---|---|
989
+ | `INVALID_INPUT` | Missing top-level required field (`workspaceId`/`workspaceName`/`semanticModelName`/`semanticModelLabel`/`label`/`fields`/`visualSpecification`); malformed filter (both `include`+`exclude`, `min`>`max`, empty date bounds); or a forecast referencing a missing/mistyped field, non-Line chart, `forecastInterval.value`≤0, or invalid `confidenceLevel`. |
990
+ | `INVALID_VISUALIZATION_METADATA` | A fieldKey used in `rows`/`columns`/an encoding/a filter isn't in the `fields` map, or an encoding type doesn't fit the mark (e.g. `Angle` outside `Donut`). |
991
+ | `JSON_PARSER_ERROR` | Body shape mismatch — `fields` must be a map (not an array); `marks.panes` is a single object (not an array of panes); `visualSpecification` must be a top-level object, not nested under `body`. |
992
+ | `UNKNOWN_EXCEPTION` | Most common fixable cause: binding a **calculated measure** with `objectName` set — drop it (§10/§16). Also the generic fallback for `MeasureNames`/`MeasureValues` missing `displayCategory`. |