@salesforce/afv-skills 1.59.0 → 1.61.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.
- package/package.json +1 -1
- package/skills/agentforce-architecture-analyze/SKILL.md +66 -17
- package/skills/agentforce-architecture-analyze/assets/cli/show_access_token.yaml +4 -7
- package/skills/agentforce-architecture-analyze/references/contract.json +1 -1
- package/skills/agentforce-architecture-analyze/scripts/emit_env.py +2 -2
- package/skills/agentforce-architecture-analyze/scripts/main.py +40 -44
- package/skills/agentforce-architecture-analyze/scripts/rest_client.py +11 -3
- package/skills/agentforce-architecture-analyze/scripts/sf_cli.py +77 -10
- package/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py +1 -0
- package/skills/agentforce-architecture-analyze/scripts/tests/test_flow_children_inflation.py +1 -0
- package/skills/agentforce-architecture-analyze/scripts/tests/test_main_pipeline.py +53 -2
- package/skills/agentforce-architecture-analyze/scripts/tests/test_resolve_creds.py +184 -36
- package/skills/agentforce-architecture-analyze/scripts/tests/test_rest_client.py +17 -0
- package/skills/agentforce-architecture-analyze/scripts/tests/test_sf_cli.py +165 -11
- package/skills/agentforce-architecture-analyze/scripts/tests/test_skill_md_pipeline_block.py +319 -0
- package/skills/agentforce-d360-analyze/SKILL.md +28 -10
- package/skills/agentforce-d360-analyze/scripts/dc.py +200 -50
- package/skills/agentforce-d360-analyze/scripts/discover_sessions.py +10 -6
- package/skills/agentforce-d360-analyze/scripts/fetch_dc.py +22 -6
- package/skills/agentforce-d360-analyze/scripts/resolve_session.py +21 -13
- package/skills/agentforce-d360-analyze/scripts/tests/test_dc_and_resolve_session.py +298 -76
- package/skills/agentforce-d360-analyze/scripts/tests/test_default_target_org.py +432 -0
- package/skills/agentforce-d360-analyze/scripts/tests/test_discover_sessions.py +1 -1
- package/skills/agentforce-d360-analyze/scripts/tests/test_resolve_session_main.py +9 -3
- package/skills/agentforce-d360-analyze/scripts/tests/test_skill_md_prefix_block.py +176 -0
- package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +17 -27
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +7 -17
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +47 -63
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +10 -13
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -46
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +4 -21
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +58 -82
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +16 -315
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +9 -24
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -44
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +11 -63
- package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +2 -1
- package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +2 -1
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +2 -1
- package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +2 -1
- package/skills/data360-schema-get/SKILL.md +3 -3
- package/skills/design-systems-slds-validate/references/report-format.md +1 -1
- package/skills/dx-org-manage/SKILL.md +1 -0
- package/skills/dx-org-shape-manage/SKILL.md +1 -0
- package/skills/dx-org-snapshot-manage/SKILL.md +172 -0
- package/skills/dx-org-snapshot-manage/examples/create_error_output.json +16 -0
- package/skills/dx-org-snapshot-manage/examples/create_success_output.json +19 -0
- package/skills/dx-org-snapshot-manage/examples/delete_output.json +9 -0
- package/skills/dx-org-snapshot-manage/examples/get_output.json +19 -0
- package/skills/dx-org-snapshot-manage/examples/list_output.json +36 -0
- package/skills/dx-org-snapshot-manage/references/cli_flags.md +179 -0
- package/skills/dx-org-snapshot-manage/references/snapshot_usage.md +55 -0
- package/skills/dx-org-trial-expiration-check/SKILL.md +1 -1
- package/skills/dx-org-trial-expiration-check/scripts/check_expiration.sh +2 -2
- package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +7 -5
- package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +11 -11
- package/skills/experience-content-media-image-generate/SKILL.md +83 -0
- package/skills/experience-content-media-image-generate/scripts/download-image.sh +84 -0
- package/skills/external-diagram-mermaid-generate/SKILL.md +12 -12
- package/skills/field-service-data-capture-migrate/references/component-mapping.md +1 -1
- package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +1 -2
- package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +1 -0
- package/skills/field-service-data-capture-migrate/references/transformer-rules.md +1 -1
- package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +12 -1
- package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +4 -1
- package/skills/field-service-setup-orchestrator-get/SKILL.md +0 -1
- package/skills/insurance-brokerage-agency-billing-configure/SKILL.md +233 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/accounting-setup.md +117 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/billing-rules-config.md +237 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/billing-settings.md +271 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/demo-policy-config.md +138 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/org-defaults.md +64 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/permissions-config.md +226 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/prerequisites.md +142 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +3 -2
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +3 -3
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +68 -45
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +17 -18
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +16 -3
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +16 -13
- package/skills/marketing-message-test/SKILL.md +194 -0
- package/skills/marketing-message-test/references/api-contract.md +333 -0
- package/skills/omnistudio-callable-apex-generate/SKILL.md +3 -3
- package/skills/omnistudio-datamapper-generate/SKILL.md +6 -6
- package/skills/omnistudio-datapacks-deploy/SKILL.md +11 -11
- package/skills/omnistudio-dependencies-analyze/SKILL.md +5 -5
- package/skills/omnistudio-epc-catalog-generate/SKILL.md +6 -6
- package/skills/omnistudio-flexcard-generate/SKILL.md +6 -6
- package/skills/omnistudio-integration-procedure-generate/SKILL.md +6 -6
- package/skills/omnistudio-omniscript-generate/SKILL.md +6 -5
- package/skills/platform-apex-logs-debug/SKILL.md +1 -0
- package/skills/platform-metadata-deploy/SKILL.md +1 -0
- package/skills/platform-models-api-configure/SKILL.md +5 -5
- package/skills/platform-soql-query/SKILL.md +1 -0
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +17 -6
- package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +107 -192
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +6 -6
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +49 -20
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +4 -4
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +224 -290
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/workflow.md +295 -0
- package/skills/service-itsm-incident-priority-configure/SKILL.md +1 -1
- package/skills/service-itsm-slack-itservice-configure/SKILL.md +2 -1
- package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +2 -2
- package/skills/service-omni-channel-setup-coordinate/SKILL.md +20 -13
- package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +5 -55
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +0 -38
- package/skills/service-omni-command-center-analyze/SKILL.md +2 -3
- package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
- package/skills/tableau-next-asset-create/README.md +65 -0
- package/skills/tableau-next-asset-create/SKILL.md +409 -0
- package/skills/tableau-next-asset-create/references/admin-users.md +149 -0
- package/skills/tableau-next-asset-create/references/ai-readiness.md +355 -0
- package/skills/tableau-next-asset-create/references/alerts.md +111 -0
- package/skills/tableau-next-asset-create/references/build-workflow.md +819 -0
- package/skills/tableau-next-asset-create/references/dashboard-authoring.md +302 -0
- package/skills/tableau-next-asset-create/references/dashboard-design-principles.md +352 -0
- package/skills/tableau-next-asset-create/references/data-transform-gotchas.md +210 -0
- package/skills/tableau-next-asset-create/references/data-understanding.md +387 -0
- package/skills/tableau-next-asset-create/references/deep-reference-index.md +74 -0
- package/skills/tableau-next-asset-create/references/destructive-operations.md +62 -0
- package/skills/tableau-next-asset-create/references/edit-dashboard.md +273 -0
- package/skills/tableau-next-asset-create/references/edit-visualization.md +529 -0
- package/skills/tableau-next-asset-create/references/empty-source-handling.md +104 -0
- package/skills/tableau-next-asset-create/references/ingest-and-metric-gotchas.md +638 -0
- package/skills/tableau-next-asset-create/references/insight-bundle.md +69 -0
- package/skills/tableau-next-asset-create/references/large-flat-file-handling.md +331 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_envelope.json +54 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_shared-widget-fields.json +122 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/add_page.json +29 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/delete_page.json +27 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/discard_dashboard.json +11 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget.json +30 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget_to_page.json +30 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/place_widget_on_page.json +30 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_dashboard.json +16 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_page.json +26 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/rename_page.json +28 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/save_dashboard.json +18 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/undo.json +11 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_button_widget.json +69 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_container_widget.json +88 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_filter_widget.json +146 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_metric_widget.json +78 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_navigation_widget.json +80 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_parameter_widget.json +160 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_text_widget.json +103 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_visualization_widget.json +68 -0
- package/skills/tableau-next-asset-create/references/sdm-tool-reference.md +721 -0
- package/skills/tableau-next-asset-create/references/semantic-query-and-enrichment.md +355 -0
- package/skills/tableau-next-asset-create/references/shared-gates.md +322 -0
- package/skills/tableau-next-asset-create/references/sharing-and-promotion.md +306 -0
- package/skills/tableau-next-asset-create/references/tasks/ai-readiness-audit.md +51 -0
- package/skills/tableau-next-asset-create/references/tasks/analyze-data.md +48 -0
- package/skills/tableau-next-asset-create/references/tasks/build-dashboard.md +66 -0
- package/skills/tableau-next-asset-create/references/tasks/build-end-to-end.md +73 -0
- package/skills/tableau-next-asset-create/references/tasks/create-data-transform.md +51 -0
- package/skills/tableau-next-asset-create/references/tasks/create-viz.md +58 -0
- package/skills/tableau-next-asset-create/references/tasks/edit-dashboard.md +80 -0
- package/skills/tableau-next-asset-create/references/tasks/edit-sdm.md +106 -0
- package/skills/tableau-next-asset-create/references/tasks/edit-viz.md +40 -0
- package/skills/tableau-next-asset-create/references/tasks/enrich-model.md +68 -0
- package/skills/tableau-next-asset-create/references/tasks/ingest-flat-file.md +68 -0
- package/skills/tableau-next-asset-create/references/tasks/manage-alert.md +35 -0
- package/skills/tableau-next-asset-create/references/tasks/profile-dataset.md +47 -0
- package/skills/tableau-next-asset-create/references/tasks/promote-or-reuse.md +46 -0
- package/skills/tableau-next-asset-create/references/tasks/provision-user.md +39 -0
- package/skills/tableau-next-asset-create/references/tasks/query-model.md +92 -0
- package/skills/tableau-next-asset-create/references/tasks/review-verified-questions.md +42 -0
- package/skills/tableau-next-asset-create/references/tasks/share-asset.md +37 -0
- package/skills/tableau-next-asset-create/references/verified-questions.md +125 -0
- package/skills/tableau-next-asset-create/references/viz-authoring.md +992 -0
- package/skills/service-de-channel-consent-configure/SKILL.md +0 -230
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +0 -102
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
# SDM build workflow — Steps 0–11
|
|
2
|
+
|
|
3
|
+
The procedural spine for building a Tableau Next semantic model end to end. Load
|
|
4
|
+
this whenever the work requires creating or extending a model. SKILL.md holds the
|
|
5
|
+
priority rules and the one-line step map that routes here; this file holds the
|
|
6
|
+
full per-step detail and verified payloads. The deeper single-topic guides
|
|
7
|
+
(`data-understanding.md`, `sdm-tool-reference.md`, `semantic-query-and-enrichment.md`,
|
|
8
|
+
`viz-authoring.md`, `dashboard-authoring.md`, `ai-readiness.md`,
|
|
9
|
+
`large-flat-file-handling.md`, `empty-source-handling.md`) are referenced inline.
|
|
10
|
+
|
|
11
|
+
## Contents
|
|
12
|
+
|
|
13
|
+
- [Workflow](#workflow)
|
|
14
|
+
- Step 0 — Ingest (CSV/Excel upload or existing database connection)
|
|
15
|
+
- Step 0.5 — Profile source schema before adding fields
|
|
16
|
+
- Step 1 — Discover candidate sources
|
|
17
|
+
- Step 2 — Verify data presence (HARD GATE — before building)
|
|
18
|
+
- Step 3 — Confirm join keys actually match
|
|
19
|
+
- Step 4 — Create the model and bind objects (one nested call)
|
|
20
|
+
- Step 5 — Confirm apiNames match expectations
|
|
21
|
+
- Step 6 — Create a relationship for every join
|
|
22
|
+
- Step 7 — Validate (BLOCKING — both checks must pass)
|
|
23
|
+
- Step 8 — Enrich: calculated fields and metrics
|
|
24
|
+
- Step 9 — Query the model (answer the question; also the Step 7 proof)
|
|
25
|
+
- Step 10 — Create visualizations (only when the user wants charts/dashboard)
|
|
26
|
+
- Step 11 — Build the dashboard
|
|
27
|
+
|
|
28
|
+
Copy this checklist into your working notes and check off each step as you complete it:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
- [ ] Step 0 — Ingest (CSV/Excel upload or existing database connection)
|
|
32
|
+
- [ ] Step 0.5 — Profile source schema before adding fields
|
|
33
|
+
- [ ] Step 1 — Discover candidate sources
|
|
34
|
+
- [ ] Step 2 — Verify data presence (HARD GATE — before building)
|
|
35
|
+
- [ ] Step 3 — Confirm join keys actually match
|
|
36
|
+
- [ ] Step 4 — Create the model and bind objects (one nested call)
|
|
37
|
+
- [ ] Step 5 — Confirm apiNames match expectations
|
|
38
|
+
- [ ] Step 6 — Create a relationship for every join
|
|
39
|
+
- [ ] Step 7 — Validate (BLOCKING — both checks must pass)
|
|
40
|
+
- [ ] Step 8 — Enrich: calculated fields and metrics
|
|
41
|
+
- [ ] Step 9 — Query the model (answer the question; also the Step 7 proof)
|
|
42
|
+
- [ ] Step 10 — Create visualizations (only when the user wants charts/dashboard)
|
|
43
|
+
- [ ] Step 11 — Build the dashboard
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Workflow
|
|
47
|
+
|
|
48
|
+
### Step 0 — Ingest (CSV/Excel upload or existing database connection)
|
|
49
|
+
|
|
50
|
+
When the user points at local files **or** a table on an existing Data Cloud
|
|
51
|
+
connection instead of already-modeled data assets, ingest them into DLOs and
|
|
52
|
+
DMOs first. The chain is fixed — **wait for status at each create**, do not
|
|
53
|
+
use row-count as a stand-in for ready. Payloads: `ingest-and-metric-gotchas.md`
|
|
54
|
+
§A (file) / §C (connection).
|
|
55
|
+
|
|
56
|
+
**File CSV / Excel — §A:**
|
|
57
|
+
`get_upload_connection` → `generate_presigned_credential` (per file) →
|
|
58
|
+
`infer_object_schema` (per file) → `create_data_stream` (per file, creates
|
|
59
|
+
the `*__dll` DLO) → **`run_data_stream` (per stream — materializes rows; do
|
|
60
|
+
this BEFORE the Step 2 row gate) + wait until the DLO is Active** →
|
|
61
|
+
**SCHEMA REALITY CHECK** (query the materialized DLO / `browse_data_assets`;
|
|
62
|
+
**not** `get_data_stream`) and copy the real Primary Key →
|
|
63
|
+
`create_data_model_object` (unsuffixed name — `Orders`, not `Orders__dlm`;
|
|
64
|
+
the platform adds `__dlm`) → **wait until the DMO is Ready** (`isEnabled:
|
|
65
|
+
false` is normal — do not gate on it) and copy the real Primary Key again →
|
|
66
|
+
`create_dlo_to_dmo_mapping` (per DMO) → inspect mapping via
|
|
67
|
+
`get_dlo_to_dmo_mapping_status` (`ERROR` / `INACTIVE`) → **poll `run_query`
|
|
68
|
+
until `<dmo>__dlm` exists** (`ACTIVE` on the status tool is metadata only;
|
|
69
|
+
`COUNT = 0` is READY).
|
|
70
|
+
|
|
71
|
+
**Existing database / lakehouse connection — §C:**
|
|
72
|
+
`list_connections` (exact `connectorType` casing) → optional
|
|
73
|
+
`test_existing_connection` (once; `9cg` id) → `list_data_connection_objects`
|
|
74
|
+
→ `infer_object_schema` (table `resourceName`; uppercase `SCHEMA`/`DATABASE`)
|
|
75
|
+
→ `create_data_stream` with `dataAccessMode: "Direct_Access"` and the
|
|
76
|
+
connection **developer name** → `run_data_stream` `interactive: false` →
|
|
77
|
+
then the same SCHEMA REALITY CHECK → DMO → mapping tail as §A.
|
|
78
|
+
|
|
79
|
+
Three non-obvious rules that each cost a failed call:
|
|
80
|
+
- **`category: "Other"`** on both the DLO (`dataLakeObjectInfo.category`) and the
|
|
81
|
+
DMO (`create_data_model_object.category`). File uploads are category-neutral;
|
|
82
|
+
`"Engagement"` or other semantic categories are wrong here.
|
|
83
|
+
- **DLO→DMO mapping field names carry a literal `__c` suffix on BOTH sides**
|
|
84
|
+
(`Order_ID__c` → `Order_ID__c`), matched 1:1. This is a *different* suffix from
|
|
85
|
+
the numeric `shouldIncludeAllFields` one (Step 5) — do not strip it or
|
|
86
|
+
numerically suffix it. Confirm the actual names from the materialized table
|
|
87
|
+
first — do not assume the CSV column survived ingestion.
|
|
88
|
+
- **The mapping isn't query-ready when `create_dlo_to_dmo_mapping` returns.**
|
|
89
|
+
Capture the returned `developerName` and call `get_dlo_to_dmo_mapping_status`
|
|
90
|
+
to inspect `fieldMappings` and to detect `ERROR` / `INACTIVE` (`ERROR` means
|
|
91
|
+
the mapping failed — do not re-POST create). `ACTIVE` is metadata deployed,
|
|
92
|
+
not "the `__dlm` table exists." Poll `run_query`
|
|
93
|
+
`SELECT 1 FROM <dmo>__dlm LIMIT 1` (or `COUNT(*)`): `42P01` / "table does
|
|
94
|
+
not exist" = keep polling; any success including `COUNT = 0` = READY.
|
|
95
|
+
`create_dlo_to_dmo_mapping` is **non-idempotent** — a duplicate call is a
|
|
96
|
+
hard error — so an ambiguous failure must be checked via the status tool
|
|
97
|
+
before assuming it needs retrying, never blindly retried.
|
|
98
|
+
|
|
99
|
+
Then build the SDM (Step 4) over the DMOs with `dataObjectType: "Dmo"` and
|
|
100
|
+
`dataObjectName: "<Entity>__dlm"`. For a DMO you just created and mapped in
|
|
101
|
+
this flow, wait until `run_query` succeeds on `__dlm` (`COUNT = 0` is READY;
|
|
102
|
+
`42P01` keep polling) — G2. Keep Step 2 as-is for any *discovered* extra
|
|
103
|
+
source. See `references/ingest-and-metric-gotchas.md` §A (file) / §C
|
|
104
|
+
(connection) for the complete per-tool payload shapes, the SCHEMA REALITY
|
|
105
|
+
CHECK, and the mapping query-readiness poll.
|
|
106
|
+
|
|
107
|
+
### Step 0.5 — Profile source schema before adding fields
|
|
108
|
+
|
|
109
|
+
Before adding any field to a model, profile the source DMO/DLO to classify
|
|
110
|
+
every field. This replaces the blind bulk import and is what lets you set clean
|
|
111
|
+
apiNames, correct types, and field-level descriptions at creation time.
|
|
112
|
+
|
|
113
|
+
**Fetch schema and sample data:**
|
|
114
|
+
|
|
115
|
+
```sql
|
|
116
|
+
SELECT * FROM "<dmo_name>__dlm" LIMIT 20
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
(use the `__dll` DLO name for a DLO). From the sample, derive per-field signals
|
|
120
|
+
and classify **every** field into one of three buckets:
|
|
121
|
+
|
|
122
|
+
**Bucket A — Business fields to include (with descriptions).** All non-system
|
|
123
|
+
fields. Subdivide by confidence:
|
|
124
|
+
- *High confidence* — clear field name + type matches role + sample values
|
|
125
|
+
available → the AI can draft the description.
|
|
126
|
+
- *Low confidence* — ambiguous name, or values carry internal meanings the AI
|
|
127
|
+
cannot infer (e.g. `status = 'NA'`) → ask the user **one targeted question**
|
|
128
|
+
per ambiguity (never a blank form).
|
|
129
|
+
|
|
130
|
+
**Bucket B — System fields to hide (`isVisible: false`).** Names matching
|
|
131
|
+
`DataSource*`, `KQ_*`, `InternalOrg*`, `*SourceVersion*`, `cdp_sys_*`. Added to
|
|
132
|
+
the model **silently** — no user interaction, and **no descriptions required**
|
|
133
|
+
(they are hidden from agents). Add them with `isVisible: false`. If a Bucket B
|
|
134
|
+
name is also a Step 3 join key (e.g. `PartKey`), still include it in the nested
|
|
135
|
+
create payload — hidden is fine; omitted is not.
|
|
136
|
+
|
|
137
|
+
**Bucket C — Type mismatch warnings.** Fields where `storageDataType` conflicts
|
|
138
|
+
with the intended semantic type:
|
|
139
|
+
- `storageDataType: Text` but values match a `YYYY-MM-DD` pattern → date stored
|
|
140
|
+
as Varchar.
|
|
141
|
+
- numeric type but values look like sequential IDs (numeric **identifiers**, not
|
|
142
|
+
measures).
|
|
143
|
+
Flag these with an explanation **before** proceeding; document the workaround in
|
|
144
|
+
`businessPreferences`. These are source-data problems the SDM layer cannot fix.
|
|
145
|
+
|
|
146
|
+
This classification feeds the nested `semanticDimensions[]` /
|
|
147
|
+
`semanticMeasurements[]` on `create_semantic_model` (Step 4). Present it to
|
|
148
|
+
the user as a markdown table **before** that create call:
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
Sales Transactions — 9 fields found
|
|
152
|
+
|
|
153
|
+
BUSINESS FIELDS (6)
|
|
154
|
+
✓ Region [Text → Dimension] High confidence
|
|
155
|
+
✓ Product [Text → Dimension] High confidence
|
|
156
|
+
✓ Amount [Number → Measure] High confidence
|
|
157
|
+
✓ Order Date [⚠️ Text, date values] Low confidence — date stored as Varchar
|
|
158
|
+
✓ Order ID [Number → Dimension] High confidence (identifier, not measure)
|
|
159
|
+
? Status [Text → Dimension] Low confidence — what do values mean?
|
|
160
|
+
|
|
161
|
+
SYSTEM FIELDS (3) — will be hidden from agents
|
|
162
|
+
DataSource__c, KQ_order_id__c, InternalOrganization__c
|
|
163
|
+
|
|
164
|
+
WARNINGS
|
|
165
|
+
⚠️ Order Date: stored as Varchar. Agents cannot do date math on this field.
|
|
166
|
+
Recommended: fix at DMO level. Workaround: DATE() calc dimension.
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Use `AskUserQuestion` for the **low-confidence Bucket A fields only** — one
|
|
170
|
+
question per ambiguity, not a form (e.g. "For `Status`, what do these values
|
|
171
|
+
mean? I see: 'Active', 'NA', 'Pending'"). Include the user's answer verbatim in
|
|
172
|
+
the field description per the AI readiness doc.
|
|
173
|
+
|
|
174
|
+
### Step 1 — Discover candidate sources
|
|
175
|
+
|
|
176
|
+
Call `browse_data_assets` with `searchTerm: ""` plus an `AssetType` filter per
|
|
177
|
+
type: `MktDataModelObject` (DMOs, e.g. `ssot__Account`),
|
|
178
|
+
`MktDataLakeObject` (DLOs, e.g. `accounts`), `MktCalculatedInsightObject`
|
|
179
|
+
(CIOs). Matching the user's words to object **labels** is how the wrong
|
|
180
|
+
objects get picked — treat the label match as a candidate list, not an answer.
|
|
181
|
+
Calling convention, the `AssetType` case-sensitivity trap, and the
|
|
182
|
+
empty-`searchTerm` matrix are in `sdm-tool-reference.md`.
|
|
183
|
+
|
|
184
|
+
### Step 2 — Verify data presence (HARD GATE — before building)
|
|
185
|
+
|
|
186
|
+
**Self-created mapping:** if the only sources are DMOs you just created and
|
|
187
|
+
mapped in Step 0 of this same flow, poll `run_query` until `<dmo>__dlm`
|
|
188
|
+
exists (`COUNT = 0` is READY; `42P01` / "table does not exist" = keep
|
|
189
|
+
polling). `get_dlo_to_dmo_mapping_status` `ACTIVE` is not this gate. Do not
|
|
190
|
+
skip the poll and proceed on metadata. Keep this gate as-is for every
|
|
191
|
+
*externally discovered* candidate (G2; `empty-source-handling.md`).
|
|
192
|
+
|
|
193
|
+
For every **discovered** candidate, check it has rows with `run_query` using the
|
|
194
|
+
source table name and suffix (DLO `__dll`, DMO `__dlm`, CIO `__dlc`):
|
|
195
|
+
|
|
196
|
+
```sql
|
|
197
|
+
SELECT COUNT(*) AS n FROM accounts__dll
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Interpret the result:
|
|
201
|
+
|
|
202
|
+
| Result | Meaning | Action |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| count > 0 | usable | eligible to build on |
|
|
205
|
+
| count = 0 | empty table (has fields, no data) | do NOT build on it — surface to user |
|
|
206
|
+
| query errors `table … does not exist` | unmaterialized (0 fields) — **an error, not a zero** | exclude; cannot be joined; tell user |
|
|
207
|
+
|
|
208
|
+
A `run_query` error like `table "ssot__Activity__dlm" does not exist` is the
|
|
209
|
+
**signal that the object is unmaterialized** — do not retry it as a transient
|
|
210
|
+
failure and do not abort the whole task; record it as unusable and move on.
|
|
211
|
+
(Pre-creation, field presence comes from `browse_data_assets` metadata or this
|
|
212
|
+
error — `list_semantic_model_dimensions` has nothing to read until Step 5.)
|
|
213
|
+
|
|
214
|
+
**Hard gate:** a measure or dimension may only go on a viz/dashboard from an
|
|
215
|
+
object with verified `count > 0`. If **no** candidate matching the request has
|
|
216
|
+
rows, STOP and tell the user the sources are empty — do not build a model that
|
|
217
|
+
will render blank (the empty-of-rows vs. empty-of-fields distinction is drawn
|
|
218
|
+
in the table above). See `references/empty-source-handling.md` for the
|
|
219
|
+
authoritative exact wording to use when reporting this to the user.
|
|
220
|
+
|
|
221
|
+
### Step 3 — Confirm join keys actually match
|
|
222
|
+
|
|
223
|
+
Find how the chosen objects relate and confirm the keys match across tables
|
|
224
|
+
using two separate `run_query` calls:
|
|
225
|
+
|
|
226
|
+
```sql
|
|
227
|
+
-- 1. total rows on the candidate left table
|
|
228
|
+
SELECT COUNT(*) AS total_left FROM opportunities__dll
|
|
229
|
+
|
|
230
|
+
-- 2. rows that survive the join
|
|
231
|
+
SELECT COUNT(*) AS matched FROM opportunities__dll o
|
|
232
|
+
JOIN accounts__dll a ON o.account_id = a.account_id
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
A key that matches ~0 rows produces an empty join — as broken as no
|
|
236
|
+
relationship. If the best candidate key matches roughly zero rows, do NOT
|
|
237
|
+
create the relationship on it: find another key, or report that the objects do
|
|
238
|
+
not actually relate. Record the matching `(leftColumn = rightColumn)` pairs for
|
|
239
|
+
Step 6.
|
|
240
|
+
|
|
241
|
+
### Step 4 — Create the model and bind objects (one nested call)
|
|
242
|
+
|
|
243
|
+
`create_semantic_model` (apiName `[A-Za-z][A-Za-z0-9_]{0,79}`, no spaces;
|
|
244
|
+
`dataspace: "default"` unless told otherwise). **Always set `description`** on
|
|
245
|
+
the model — it is a first-class AI readiness requirement. If it returns `Label of
|
|
246
|
+
SemanticModel is required and cannot be empty`, that error is misleading — it
|
|
247
|
+
means the **apiName is malformed** (spaces or invalid characters), not that a
|
|
248
|
+
label is missing; fix the apiName.
|
|
249
|
+
|
|
250
|
+
**Bind fields in this same call.** Nested `semanticDataObjects[]` accept
|
|
251
|
+
explicit `semanticDimensions[]` / `semanticMeasurements[]` with
|
|
252
|
+
`shouldIncludeAllFields: false`. Build those arrays from the Step 0.5
|
|
253
|
+
classification **and** the Step 3 join keys, then send **one**
|
|
254
|
+
`create_semantic_model`. Do **not** follow with N
|
|
255
|
+
`add_semantic_model_dimension` / `_measure` calls (that is the 27-call bind
|
|
256
|
+
path). Do **not** use `shouldIncludeAllFields: true` — the flag is deprecated
|
|
257
|
+
and suffixes every apiName.
|
|
258
|
+
|
|
259
|
+
**Register the model with a workspace immediately after create succeeds.**
|
|
260
|
+
Call `add_workspace_asset` with the semantic model's returned id as `assetId`,
|
|
261
|
+
`assetType: "SemanticModel"`, and `assetUsageType: "Created"`. If the user did
|
|
262
|
+
not explicitly name a target workspace, call `list_workspaces` and **STOP to
|
|
263
|
+
ask the user which workspace to use** — do not infer or guess one.
|
|
264
|
+
|
|
265
|
+
For objects beyond the first few (bulk-create timeout at ≥ ~5
|
|
266
|
+
`semanticDataObjects[]`), create with the **anchor object and its nested
|
|
267
|
+
fields**, then `add_semantic_model_data_object` with the same nested
|
|
268
|
+
`semanticDimensions[]` / `semanticMeasurements[]` shape for each additional
|
|
269
|
+
object. If a table has no FK to the anchor, add the intermediate first, then
|
|
270
|
+
bridge through it.
|
|
271
|
+
|
|
272
|
+
**AI readiness requirements for `create_semantic_model`:**
|
|
273
|
+
- `description`: Required for AI readiness. Write a 1–2 sentence description
|
|
274
|
+
that states the model's goals and primary use cases (e.g. `"Tracks open
|
|
275
|
+
pipeline opportunities and associated account data for sales forecasting and
|
|
276
|
+
rep performance analysis"`). Include what the model is used for, not just
|
|
277
|
+
what data it contains.
|
|
278
|
+
- `categories`: Set to the relevant product category (`["Sales"]`,
|
|
279
|
+
`["Marketing"]`, etc.) where determinable from context.
|
|
280
|
+
- `agentEnabled`: Set to `true` explicitly. This gates whether AI agents
|
|
281
|
+
(Concierge / Agentforce for Analytics) can use this model.
|
|
282
|
+
- `businessPreferences`: One `# `-prefixed line per statement (hash + space),
|
|
283
|
+
matching the dedicated tools and the Edit Business Preferences UI. At
|
|
284
|
+
minimum include what the core measure means and any non-obvious terminology,
|
|
285
|
+
e.g.
|
|
286
|
+
|
|
287
|
+
```text
|
|
288
|
+
# Revenue = sum of Amount on closed-won opportunities only
|
|
289
|
+
# Stage names: 0-Prospecting through 6-Closed Won
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Prefer `get_semantic_model_business_preferences` (`includeModelContent=false`)
|
|
293
|
+
then `update_semantic_model_business_preferences` (full replacement of that
|
|
294
|
+
string; send only `businessPreferences`). Fallback: set at create or via
|
|
295
|
+
`update_semantic_model` using the **same** `# `-prefixed-line format. **When the user provides no
|
|
296
|
+
domain context** (e.g. they say only "make it AI-ready" without explaining
|
|
297
|
+
the business meaning of fields): infer at least one substantive statement
|
|
298
|
+
from the column names and object. For example, for a `sales_data` object
|
|
299
|
+
with columns `amount`, `region`, `order_date`, write:
|
|
300
|
+
|
|
301
|
+
```text
|
|
302
|
+
# amount = the transaction sale value
|
|
303
|
+
# region = the sales territory on the record, verify with domain owner whether this is geographic region or sales org territory
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Do not use a generic placeholder — that gives agents nothing
|
|
307
|
+
to work with. If you cannot infer meaningful context from the schema, ask
|
|
308
|
+
the user one targeted question: "To set businessPreferences, what does
|
|
309
|
+
`amount` represent and are there any non-obvious field values I should
|
|
310
|
+
know about?"
|
|
311
|
+
|
|
312
|
+
**AI readiness requirements for nested data objects** (same fields on
|
|
313
|
+
`create_semantic_model.semanticDataObjects[]` or a later
|
|
314
|
+
`add_semantic_model_data_object`):
|
|
315
|
+
- `description`: Required for AI readiness. Describe what this entity
|
|
316
|
+
represents (e.g. `"Active sales opportunities with stage and amount"`).
|
|
317
|
+
- `primaryNameField`: The field the AI uses to identify records in insight
|
|
318
|
+
narratives. Use `"Name"` for standard CRM objects; for file-upload DMOs use
|
|
319
|
+
the column that identifies the record (e.g. `"Product_Name"`, `"Account_Name"`).
|
|
320
|
+
**Known server bug:** `primaryNameField` cannot be set at creation time — the
|
|
321
|
+
server validates it against the field registry, which is always empty when the
|
|
322
|
+
data object is first created. This fails with `"<field> does not exist in
|
|
323
|
+
<object> fields"` regardless of bind mode. Document as a known gap; omit it
|
|
324
|
+
from the create / add-data-object call.
|
|
325
|
+
|
|
326
|
+
**Nested field arrays (Bucket A, B, and join keys)**
|
|
327
|
+
|
|
328
|
+
Put every classified field into `semanticDimensions[]` / `semanticMeasurements[]`
|
|
329
|
+
on the data object — **including Step 3 join keys** (e.g. `PartKey`,
|
|
330
|
+
`account_id__c`) even when they look like system columns. Omitting a join key
|
|
331
|
+
makes `add_semantic_model_relationship` fail with "field could not be found".
|
|
332
|
+
You control each `apiName` (clean, no suffix). Do not call
|
|
333
|
+
`add_semantic_model_dimension` / `_measure` for this initial bind; those tools
|
|
334
|
+
are only for adding a *single extra field* to an already-bound object.
|
|
335
|
+
|
|
336
|
+
Worked one-call payload (anchor object): `sdm-tool-reference.md`
|
|
337
|
+
(`create_semantic_model`). Extra objects after the timeout floor use the same
|
|
338
|
+
nested arrays on `add_semantic_model_data_object`:
|
|
339
|
+
|
|
340
|
+
```jsonc
|
|
341
|
+
{
|
|
342
|
+
"modelApiNameOrId": "Pipeline_Analysis",
|
|
343
|
+
"apiName": "Opportunities",
|
|
344
|
+
"dataObjectName": "opportunities__dlm", // __dll DLO, __dlm DMO, __dlc CIO
|
|
345
|
+
"dataObjectType": "Dmo",
|
|
346
|
+
"label": "Opportunities",
|
|
347
|
+
"description": "Active sales opportunities with stage, amount, and close date",
|
|
348
|
+
"tableType": "Standard",
|
|
349
|
+
"shouldIncludeAllFields": false,
|
|
350
|
+
"semanticDimensions": [ /* Bucket A dims + join keys + Bucket B */ ],
|
|
351
|
+
"semanticMeasurements": [ /* Bucket A measures */ ]
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Bucket A dimensions:** set `apiName` clean with no suffix, `label`,
|
|
356
|
+
`dataObjectFieldName` (the original `__c` column name), `dataType` (validated
|
|
357
|
+
against `storageDataType`), `displayCategory`, `description`, and
|
|
358
|
+
`isVisible: true`.
|
|
359
|
+
|
|
360
|
+
```jsonc
|
|
361
|
+
{
|
|
362
|
+
"apiName": "Region", // clean, no suffix
|
|
363
|
+
"label": "Region",
|
|
364
|
+
"dataObjectFieldName": "region__c",
|
|
365
|
+
"dataType": "Text", // validated against storageDataType
|
|
366
|
+
"displayCategory": "Discrete",
|
|
367
|
+
"description": "Sales territory for the transaction. Values: West, East, North, South.",
|
|
368
|
+
"isVisible": true
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Bucket A measures:** set `aggregationType` explicitly (**never `Auto`** on a
|
|
373
|
+
measure), `description`, `sentiment` for directional measures, and
|
|
374
|
+
`isVisible: true`.
|
|
375
|
+
|
|
376
|
+
```jsonc
|
|
377
|
+
{
|
|
378
|
+
"apiName": "Amount",
|
|
379
|
+
"label": "Amount",
|
|
380
|
+
"dataObjectFieldName": "amount__c",
|
|
381
|
+
"dataType": "Number",
|
|
382
|
+
"aggregationType": "Sum", // explicit, never Auto on a measure
|
|
383
|
+
"description": "Gross transaction sale value in USD. Primary revenue measure.",
|
|
384
|
+
"sentiment": "SentimentTypeUpIsGood",
|
|
385
|
+
"isVisible": true
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Critical fix — numeric identifiers.** Fields like `order_id__c` that are
|
|
390
|
+
numeric but are **identifiers** (high cardinality, sequential) must be added as
|
|
391
|
+
**dimensions** with `displayCategory: "Discrete"` and a description noting
|
|
392
|
+
"Record identifier — do not aggregate." This is what prevents an agent from
|
|
393
|
+
summing order IDs and returning a meaningless number. It also makes the field
|
|
394
|
+
usable as a metric `identifyingDimension` (bulk import ingested it as a Sum
|
|
395
|
+
measure and broke this).
|
|
396
|
+
|
|
397
|
+
```jsonc
|
|
398
|
+
{
|
|
399
|
+
"apiName": "Order_ID",
|
|
400
|
+
"label": "Order ID",
|
|
401
|
+
"dataObjectFieldName": "order_id__c",
|
|
402
|
+
"dataType": "Number",
|
|
403
|
+
"displayCategory": "Discrete", // signals: group by, don't aggregate
|
|
404
|
+
"description": "Unique numeric identifier for the sales order. Record identifier — do not aggregate.",
|
|
405
|
+
"isVisible": true
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**Bucket B system fields** (`isVisible: false`): no description needed — they
|
|
410
|
+
are hidden from agents. If a Bucket B name is also a Step 3 join key, still
|
|
411
|
+
include it (hidden is fine; omitted is not).
|
|
412
|
+
|
|
413
|
+
```jsonc
|
|
414
|
+
{
|
|
415
|
+
"apiName": "DataSource",
|
|
416
|
+
"label": "Data Source",
|
|
417
|
+
"dataObjectFieldName": "DataSource__c",
|
|
418
|
+
"dataType": "Text",
|
|
419
|
+
"isVisible": false // hidden from agents; no description needed
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**Bucket C Varchar dates.** Add the raw field as `Text` with a description
|
|
424
|
+
noting it is not suitable for date math, then **immediately** add a calculated
|
|
425
|
+
dimension using `DATE([Object].[RawField])` to produce the canonical date field.
|
|
426
|
+
The raw field stays visible but clearly non-recommended; the calc dimension is
|
|
427
|
+
the field agents should use — no ambiguity. The calc itself is still a
|
|
428
|
+
post-create `add_semantic_model_calculated_dimension` (Step 8) — nest only the
|
|
429
|
+
raw `Text` field in the create payload.
|
|
430
|
+
|
|
431
|
+
```jsonc
|
|
432
|
+
{
|
|
433
|
+
"apiName": "Order_Date_Raw",
|
|
434
|
+
"label": "Order Date (Raw Text)",
|
|
435
|
+
"dataObjectFieldName": "order_date__c",
|
|
436
|
+
"dataType": "Text",
|
|
437
|
+
"description": "Order date stored as text (YYYY-MM-DD format). NOT suitable for date arithmetic. Use Order_Date calculated dimension for time-based analysis.",
|
|
438
|
+
"isVisible": true
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
```jsonc
|
|
443
|
+
{
|
|
444
|
+
"apiName": "Order_Date",
|
|
445
|
+
"label": "Order Date",
|
|
446
|
+
"expression": "DATE([Sales_Transactions].[Order_Date_Raw])",
|
|
447
|
+
"description": "Order date, parsed from text into Date type for time-based filtering and grouping."
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Step 5 — Confirm apiNames match expectations
|
|
452
|
+
|
|
453
|
+
With nested explicit fields (Step 4), apiNames are the values you set — they
|
|
454
|
+
are clean and known. Step 5 is a **light confirmation**: confirm the first few
|
|
455
|
+
match expectations with `list_semantic_model_dimensions` before writing
|
|
456
|
+
relationships or calculated fields.
|
|
457
|
+
|
|
458
|
+
**If you inherited a bulk-bound object (`shouldIncludeAllFields: true`):**
|
|
459
|
+
|
|
460
|
+
`shouldIncludeAllFields: true` **numerically suffixes every field apiName**
|
|
461
|
+
(`account_id__c` → `account_id5`) unpredictably, and the suffix differs per
|
|
462
|
+
object — the same logical column can become `account_id5` on one object and
|
|
463
|
+
`account_id3` on another. For every object to be joined, call
|
|
464
|
+
`list_semantic_model_dimensions` (and `list_semantic_model_data_objects` for
|
|
465
|
+
object apiNames) and copy the exact stored apiName. Never construct or assume
|
|
466
|
+
the suffixed name, and never assume the two sides of a join share a suffix.
|
|
467
|
+
Do not choose this bind for new objects — see the decision table.
|
|
468
|
+
|
|
469
|
+
### Decision Table: How to bind fields
|
|
470
|
+
|
|
471
|
+
| Scenario | Approach |
|
|
472
|
+
|---|---|
|
|
473
|
+
| New SDM | Nested explicit fields in `create_semantic_model` (`shouldIncludeAllFields: false`). One call. |
|
|
474
|
+
| ≥ ~5 objects (bulk-create timeout) | Create with the anchor object + its nested fields; then `add_semantic_model_data_object` with nested arrays per extra object |
|
|
475
|
+
| Add an object to an existing SDM | Nested explicit fields on `add_semantic_model_data_object` (`shouldIncludeAllFields: false`) |
|
|
476
|
+
| Add one field to an already-bound object | `add_semantic_model_dimension` / `_measure` |
|
|
477
|
+
| Wide DMO (60+ fields) | Nest the top 15–20 business fields + **all join keys** + hidden Bucket B; omit niche columns rather than bulk-importing them undescribed |
|
|
478
|
+
| HardJoin logical view | `shouldIncludeAllFields: true` still — apiNames are assigned at first save (read back before joining) |
|
|
479
|
+
| NEVER | `shouldIncludeAllFields: true` on model-level create/add (deprecated; suffixes every apiName) |
|
|
480
|
+
|
|
481
|
+
### Step 6 — Create a relationship for every join
|
|
482
|
+
|
|
483
|
+
For each key pair from Step 3, call `add_semantic_model_relationship` using the
|
|
484
|
+
**semantic dimension apiName you set in Step 4** (light-confirmed in Step 5)
|
|
485
|
+
with `leftFieldType: "TableField"` — NOT the underlying `dataObjectFieldName`
|
|
486
|
+
(that returns "field could not be found"). Because you controlled the apiName
|
|
487
|
+
at bind time, this resolves first-try. The apiNames below are illustrative —
|
|
488
|
+
replace them with the exact values from Step 4 / Step 5:
|
|
489
|
+
|
|
490
|
+
```jsonc
|
|
491
|
+
{
|
|
492
|
+
"modelApiNameOrId": "Pipeline_Analysis",
|
|
493
|
+
"apiName": "Opp_To_Account",
|
|
494
|
+
"label": "Opportunity to Account", // REQUIRED (schema says optional; server rejects without it)
|
|
495
|
+
"joinType": "Auto", // MUST be Auto at model level
|
|
496
|
+
"cardinality": "ManyToOne",
|
|
497
|
+
"leftSemanticDefinitionApiName": "Opportunities",
|
|
498
|
+
"rightSemanticDefinitionApiName": "Accounts",
|
|
499
|
+
"criteria": [{
|
|
500
|
+
"joinOperator": "Equals",
|
|
501
|
+
"leftFieldType": "TableField",
|
|
502
|
+
"leftSemanticFieldApiName": "<Opportunities account-FK apiName from Step 5>",
|
|
503
|
+
"rightFieldType": "TableField",
|
|
504
|
+
"rightSemanticFieldApiName": "<Accounts Primary Key apiName from Step 5>"
|
|
505
|
+
}]
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Step 7 — Validate (BLOCKING — both checks must pass)
|
|
510
|
+
|
|
511
|
+
1. `list_semantic_model_relationships` → confirm **every** object from Step 4
|
|
512
|
+
appears on the left or right of at least one relationship. Any object in no
|
|
513
|
+
relationship is an island — return to Step 6.
|
|
514
|
+
2. `run_semantic_query` for a real measure grouped by a dimension (traversing a
|
|
515
|
+
join when there is more than one object). In this query, reference tables by
|
|
516
|
+
the **SDM data-object apiName** (from `list_semantic_model_data_objects`),
|
|
517
|
+
NOT the `__dll`/`__dlm` SQL name used by `run_query` — the underlying name is
|
|
518
|
+
rejected with `Failed to resolve table`, which is a query-construction bug,
|
|
519
|
+
not an empty-source signal. **If the result is genuinely empty, the model is
|
|
520
|
+
not shippable: do NOT create dashboards or viz — STOP and report to the
|
|
521
|
+
user.** An empty result means an empty source (Step 2) or a non-matching key
|
|
522
|
+
(Step 3) got through. This check is blocking even for a single-object model.
|
|
523
|
+
|
|
524
|
+
**After enrichment (Step 8), before shipping:** If you have added calculated
|
|
525
|
+
fields or metrics, use the Semantic Model AI Optimization similarity scan (in
|
|
526
|
+
Tableau Next's Semantic Model Builder UI) to check for overlapping or ambiguous
|
|
527
|
+
components. A `modelHealth: Low` result indicates fields that agents will
|
|
528
|
+
struggle to differentiate — resolve by improving descriptions or removing
|
|
529
|
+
redundant components before exposing the model to AI users.
|
|
530
|
+
|
|
531
|
+
### Step 8 — Enrich: calculated fields and metrics
|
|
532
|
+
|
|
533
|
+
Raw fields are often not the answer the user asked for — engagement,
|
|
534
|
+
margin, win-rate, "deal size" are **derived**. Add them so the model
|
|
535
|
+
answers the real question, not just exposes columns.
|
|
536
|
+
|
|
537
|
+
**Identify the analysis intent first** (trend / comparison / part-to-whole /
|
|
538
|
+
correlation / forecast — see the Step 10 map in `tasks/create-viz.md`) so
|
|
539
|
+
enrichment produces the fields that intent needs: a business-meaningful date
|
|
540
|
+
anchor for trend/forecast, a slicing dimension for part-to-whole, two measures
|
|
541
|
+
for correlation.
|
|
542
|
+
|
|
543
|
+
**Create a metric by default when the request implies a tracked business
|
|
544
|
+
number AND a meaningful time dimension exists to anchor it.** Analytical asks
|
|
545
|
+
("analyze account engagement", "pipeline health", "revenue by region", "win
|
|
546
|
+
rate over time") almost always want a named, time-bound **KPI** — a metric —
|
|
547
|
+
not just a raw column. A common miss is building the model and stopping; if
|
|
548
|
+
the user's intent names or implies a headline number, create the metric for it.
|
|
549
|
+
|
|
550
|
+
The time anchor must be **business-meaningful** — a real event date like
|
|
551
|
+
created/close/order/activity date — NOT a plumbing column. Skip the metric (or
|
|
552
|
+
ask) when the only date fields are system/load timestamps (e.g.
|
|
553
|
+
`cdp_sys_PartitionDate`, `*_SourceVersion`, ingestion timestamps), or when the
|
|
554
|
+
request is purely structural ("just model these tables"). A metric anchored on
|
|
555
|
+
a junk date is worse than no metric.
|
|
556
|
+
|
|
557
|
+
**For AI-readiness requirements** on the three component types below —
|
|
558
|
+
descriptions, value-meaning pairs, sentiment enums, singularNoun/pluralNoun,
|
|
559
|
+
insightsDimensionsReferences, pre-creation `list_*` duplicate checks, and the
|
|
560
|
+
unique-purpose test — see [ai-readiness.md](ai-readiness.md). What follows
|
|
561
|
+
here is build-order and payload mechanics only.
|
|
562
|
+
|
|
563
|
+
- **Calculated dimension** (derived grouping, e.g. bucketing, `UPPER(...)`,
|
|
564
|
+
date-part): `add_semantic_model_calculated_dimension`. Use TuA bracketed
|
|
565
|
+
references `[DataObjectApiName].[FieldApiName]` with the **suffixed**
|
|
566
|
+
apiNames from Step 5 — bare names fail with "Missing reference". Row-level
|
|
567
|
+
expressions get `level: "Row"` automatically.
|
|
568
|
+
- **Calculated measure** (derived numeric, e.g. ratio, average):
|
|
569
|
+
`add_semantic_model_calculated_measure`. For an expression that already
|
|
570
|
+
aggregates (`AVG(...)`, `SUM(...)/COUNT(...)`, `COUNTD(...)`), set
|
|
571
|
+
`aggregationType: "UserAgg"` — a level-aware aggregation with a bare
|
|
572
|
+
`Average`/`Sum` is rejected with "AggregativeFunction-level calculated
|
|
573
|
+
fields require UserAgg". A row-level arithmetic expression
|
|
574
|
+
(`[O].[Profit] / [O].[Sales]`) can use `aggregationType: "Auto"`.
|
|
575
|
+
- **Metric** (named, time-bound KPI — create one by default per above):
|
|
576
|
+
`add_semantic_model_metric`. Set exactly one of
|
|
577
|
+
`measurementReference.calculatedFieldApiName` (a calc measure) or
|
|
578
|
+
`measurementReference.tableFieldReference {tableApiName, fieldApiName}`
|
|
579
|
+
(a raw measure, suffixed apiName); add a `timeDimensionReference` pointing
|
|
580
|
+
at a **business-meaningful Date/DateTime** dimension (find candidates via
|
|
581
|
+
`list_semantic_model_dimensions` — `dataType: "Date"` — and pick a real event
|
|
582
|
+
date like created/close/order date, not a `cdp_sys_*`/load timestamp) and
|
|
583
|
+
`timeGrains`. Skip the metric only if no meaningful date dimension resolves.
|
|
584
|
+
Three mechanical rules — each costs a rejected call or a broken metric:
|
|
585
|
+
1. **`insightsSettings.identifyingDimension` is REQUIRED** (point it at the
|
|
586
|
+
grain's primary key). The server accepts a metric without it (create
|
|
587
|
+
returns 201) but it then crashes the Tableau Next metric UI on open — a
|
|
588
|
+
downstream failure a "did the call succeed?" check won't catch. Always
|
|
589
|
+
set it; see ai-readiness.md for the full explanation.
|
|
590
|
+
2. **`aggregationType` may NEVER be `Auto` on a metric** ("The aggregation type
|
|
591
|
+
(Auto) is not allowed for metric aggregation"). Use a concrete enum: `Sum`,
|
|
592
|
+
`Count`, `Average`, `Min`, `Max`. A metric over a *row-level ratio* calc
|
|
593
|
+
measure (e.g. `Profit_Margin`) takes `Average`, not `Auto`.
|
|
594
|
+
3. **To count records, point `measurementReference` at a measurable (numeric)
|
|
595
|
+
field with `aggregationType: "Count"` — NOT at a dimension/Primary Key.** Counting via
|
|
596
|
+
`Order_ID` is rejected ("field name: (Order_ID) was not found in the model"
|
|
597
|
+
— it's a dimension). Use a numeric column (e.g. `Sales`) + `Count`; it counts
|
|
598
|
+
one row per record. The `identifyingDimension` can still be the Primary Key.
|
|
599
|
+
|
|
600
|
+
Verified shape (AI-ready):
|
|
601
|
+
|
|
602
|
+
```jsonc
|
|
603
|
+
{
|
|
604
|
+
"modelApiNameOrId": "Pipeline_Analysis",
|
|
605
|
+
"apiName": "Total_Pipeline",
|
|
606
|
+
"label": "Total Pipeline",
|
|
607
|
+
"description": "Total open pipeline amount summed across active opportunities",
|
|
608
|
+
"measurementReference": { "tableFieldReference": { "tableApiName": "Opportunities", "fieldApiName": "Amount7" } },
|
|
609
|
+
"timeDimensionReference": { "tableFieldReference": { "tableApiName": "Opportunities", "fieldApiName": "Created_Date14" } },
|
|
610
|
+
"aggregationType": "Sum",
|
|
611
|
+
"timeGrains": ["Day", "Week", "Month", "Quarter", "Year"],
|
|
612
|
+
"sentiment": "SentimentTypeUpIsGood",
|
|
613
|
+
"insightsSettings": {
|
|
614
|
+
"identifyingDimension": {
|
|
615
|
+
"identifierDimensionReference": {
|
|
616
|
+
"tableFieldReference": { "tableApiName": "Opportunities", "fieldApiName": "Opportunity_Id5" }
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
"sentiment": "SentimentTypeUpIsGood",
|
|
620
|
+
"singularNoun": "opportunity",
|
|
621
|
+
"pluralNoun": "opportunities",
|
|
622
|
+
"insightsDimensionsReferences": [
|
|
623
|
+
{ "tableFieldReference": { "tableApiName": "Opportunities", "fieldApiName": "Stage8" } },
|
|
624
|
+
{ "tableFieldReference": { "tableApiName": "Accounts", "fieldApiName": "Region4" } }
|
|
625
|
+
]
|
|
626
|
+
},
|
|
627
|
+
"additionalDimensions": [
|
|
628
|
+
{ "tableFieldReference": { "tableApiName": "Opportunities", "fieldApiName": "Opportunity_Id5" } },
|
|
629
|
+
{ "tableFieldReference": { "tableApiName": "Opportunities", "fieldApiName": "Stage8" } },
|
|
630
|
+
{ "tableFieldReference": { "tableApiName": "Accounts", "fieldApiName": "Region4" } }
|
|
631
|
+
]
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
`additionalDimensions` **must mirror every field in `insightsSettings.identifyingDimension`,
|
|
635
|
+
`insightsDimensionsReferences`, and `filters`** — create validates the insight
|
|
636
|
+
dims (`Insight dimension (...) is missing from the metric additional dimensions`);
|
|
637
|
+
omitting a filter field makes the metric unqueryable
|
|
638
|
+
(`Metric Definition Filter Field ... is not found`).
|
|
639
|
+
**Exception: the server rejects `Date`-type fields in `additionalDimensions`**
|
|
640
|
+
(`"Field should have one of these data types: Text, Number, Boolean, Email,
|
|
641
|
+
PhoneNumber, Url. Actual data type: Date"`). If a filter field is a Date, do
|
|
642
|
+
not add it to `additionalDimensions`.
|
|
643
|
+
`fieldApiName` on the measurement/time/identifier references is the
|
|
644
|
+
**suffixed** apiName from the owning object (Step 5) — a date field from a
|
|
645
|
+
*different* object is rejected with "Table field … was not found in the
|
|
646
|
+
model".
|
|
647
|
+
|
|
648
|
+
If a calc measure references a calc dimension, create the dimension first.
|
|
649
|
+
Build calc fields only on objects that passed the Step 2 row check — a
|
|
650
|
+
derived field over an empty source is still empty.
|
|
651
|
+
|
|
652
|
+
**Step 8 closing checklist — run before proceeding to Step 9:**
|
|
653
|
+
|
|
654
|
+
For every visible raw measure in the model, verify one of the following is
|
|
655
|
+
true — if neither is, create the missing metric:
|
|
656
|
+
|
|
657
|
+
| Check | Pass condition |
|
|
658
|
+
|---|---|
|
|
659
|
+
| A metric already covers this measure | A metric's `measurementReference` points at this field (or a calc measure derived from it) |
|
|
660
|
+
| No meaningful time anchor exists | The only date fields are system/load timestamps (`cdp_sys_*`, `*SourceVersion`, ingestion columns) |
|
|
661
|
+
| Request is purely structural | User explicitly asked to "just model the tables" with no analytical intent |
|
|
662
|
+
|
|
663
|
+
This sweep must cover **every** visible raw measure — not just the first
|
|
664
|
+
one. The failure mode is anchoring on the primary KPI and stopping,
|
|
665
|
+
leaving secondary measures (e.g. `Amount` alongside `Engagement_Score`)
|
|
666
|
+
without metrics. A missing metric means agents fall back to raw field
|
|
667
|
+
aggregation, losing time-scoping, period comparison, and insight
|
|
668
|
+
breakdown context.
|
|
669
|
+
|
|
670
|
+
### Step 9 — Query the model (answer the question; also the Step 7 proof)
|
|
671
|
+
|
|
672
|
+
**Before constructing a raw `run_semantic_query`:** if the user's question
|
|
673
|
+
implies a derived or named business number (e.g. "average order value",
|
|
674
|
+
"win rate", "gross margin"), call `list_semantic_model_calculated_measures`
|
|
675
|
+
and `list_semantic_model_metrics` first. If a calculated measure or metric
|
|
676
|
+
already satisfies the request, reference it directly rather than
|
|
677
|
+
reconstructing the aggregation from raw fields:
|
|
678
|
+
- A calculated measure → `semantic_field: { name: "<calc_measure_apiName>" }`
|
|
679
|
+
with `semantic_aggregation_method: "SEMANTIC_AGGREGATION_METHOD_USER_AGG"`
|
|
680
|
+
(for UserAgg expressions).
|
|
681
|
+
- A metric → use `run_semantic_query` with `structuredMetricQuery` (see shape below).
|
|
682
|
+
|
|
683
|
+
Do NOT bypass an existing `Avg_Order_Value` calculated measure to write
|
|
684
|
+
`AVG(Amount)` from a raw table field — the calculated measure encodes the
|
|
685
|
+
authoritative business definition.
|
|
686
|
+
|
|
687
|
+
`run_semantic_query` both validates the model (Step 7) and answers the user's
|
|
688
|
+
data question from it. Our MCP uses a **proto-shaped snake_case** body — NOT
|
|
689
|
+
the REST gateway's camelCase.
|
|
690
|
+
|
|
691
|
+
**Querying a metric** (`structuredMetricQuery`) — verified working shape:
|
|
692
|
+
|
|
693
|
+
```jsonc
|
|
694
|
+
{
|
|
695
|
+
"semanticModelApiName": "Pipeline_Analysis",
|
|
696
|
+
"source": "tableau-next-pilot-mcp",
|
|
697
|
+
"structuredMetricQuery": {
|
|
698
|
+
"submetric_definition": { "metric_api_name": "Total_Pipeline" },
|
|
699
|
+
"time_grain": "Month" // Day | Week | Month | Quarter | Year
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
`metric_api_name` is the only accepted key inside `submetric_definition` — all
|
|
705
|
+
other variants (`metricApiName`, `metricName`, `api_name`, `name`, `metric`) are
|
|
706
|
+
rejected with `Cannot find field`. `time_grain` is required.
|
|
707
|
+
|
|
708
|
+
**Querying raw fields** (`structuredSemanticQuery`) — verified working shape:
|
|
709
|
+
|
|
710
|
+
```jsonc
|
|
711
|
+
{
|
|
712
|
+
"semanticModelApiName": "Pipeline_Analysis",
|
|
713
|
+
"source": "tableau-next-pilot-mcp",
|
|
714
|
+
"structuredSemanticQuery": {
|
|
715
|
+
"fields": [
|
|
716
|
+
{ "expression": { "table_field": { "name": "region18", "table_name": "Accounts" } },
|
|
717
|
+
"alias": "Accounts.region18", "grouping": "ROW_GROUPING" },
|
|
718
|
+
{ "expression": { "table_field": { "name": "Amount7", "table_name": "Opportunities" } },
|
|
719
|
+
"alias": "Opportunities.Amount7",
|
|
720
|
+
"semantic_aggregation_method": "SEMANTIC_AGGREGATION_METHOD_SUM" }
|
|
721
|
+
],
|
|
722
|
+
"options": { "limit_options": { "limit": 10 },
|
|
723
|
+
"sort_orders": [ { "simple_sort_order":
|
|
724
|
+
{ "sort_by_field_alias": "Opportunities.Amount7", "sorting_order": "DESC" } } ] }
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
Rules that differ from raw SQL and from the REST gateway:
|
|
730
|
+
- Reference tables by the **SDM data-object apiName** (`table_field.table_name`),
|
|
731
|
+
never the `__dll`/`__dlm` name. The latter → `Failed to resolve table`.
|
|
732
|
+
- Dimensions carry `grouping: "ROW_GROUPING"`; raw measures carry
|
|
733
|
+
`semantic_aggregation_method: "SEMANTIC_AGGREGATION_METHOD_<SUM|AVG|COUNT|MIN|MAX>"`.
|
|
734
|
+
- A **calculated** field is referenced by `semantic_field: { name }` (no
|
|
735
|
+
`table_name`); a UserAgg calc measure uses
|
|
736
|
+
`semantic_aggregation_method: "SEMANTIC_AGGREGATION_METHOD_USER_AGG"`.
|
|
737
|
+
- `grand_total`, `limit`, `sort_orders` all live **inside** `options` — at the
|
|
738
|
+
top level they fail with `Cannot find field`.
|
|
739
|
+
|
|
740
|
+
The shapes above cover the common cases. See
|
|
741
|
+
`references/semantic-query-and-enrichment.md` for the full field-shape matrix,
|
|
742
|
+
aggregation-method mapping, and worked enrichment payloads when a
|
|
743
|
+
`run_semantic_query` or `add_semantic_model_calculated_*` /
|
|
744
|
+
`add_semantic_model_metric` call falls outside them.
|
|
745
|
+
|
|
746
|
+
See `references/sdm-tool-reference.md` for the complete tool list and the full
|
|
747
|
+
suffix-rule / apiName-mutation reference (the specific mutation gotcha is
|
|
748
|
+
already called out inline at Step 5 above).
|
|
749
|
+
|
|
750
|
+
### Step 10 — Create visualizations (only when the user wants charts/dashboard)
|
|
751
|
+
|
|
752
|
+
**MANDATORY — design the narrative before touching a chart tool.** Before
|
|
753
|
+
calling `create_visualization`, design the story: KPIs → trends → breakdowns →
|
|
754
|
+
correlations, use business-friendly labels (never raw/technical field names),
|
|
755
|
+
pick diverse chart types (not every widget as a bar chart), and plan KPIs
|
|
756
|
+
top/left in the eventual layout. This is a hard gate alongside the Step 2/9
|
|
757
|
+
data-presence check (row count > 0) — do not build on an empty source. Read
|
|
758
|
+
`references/dashboard-design-principles.md` (MANDATORY, not optional) for the
|
|
759
|
+
full narrative-design guidance before proceeding.
|
|
760
|
+
|
|
761
|
+
Do this only after Step 9 returns real data — never chart a model that queried
|
|
762
|
+
empty.
|
|
763
|
+
|
|
764
|
+
**First, identify the user's analysis intent — it picks the analysis type, which
|
|
765
|
+
picks the chart.** Don't jump straight to a bar chart. Full intent → type →
|
|
766
|
+
tool map (including Line + `visualSpecification.forecasts` for predictive asks)
|
|
767
|
+
lives in `tasks/create-viz.md` / `viz-authoring.md` §3; payloads are in
|
|
768
|
+
`viz-authoring.md` §11.
|
|
769
|
+
|
|
770
|
+
One tool handles all chart types: `create_visualization`. It uses a
|
|
771
|
+
**`fields` map + `visualSpecification`** shape — NOT the `fieldRef /
|
|
772
|
+
measureFunction` shape from older tools. `objectName` is the **SDM
|
|
773
|
+
data-object apiName** (`Orders`), `fieldName` is the field apiName on that
|
|
774
|
+
object — use the **suffixed** apiName (Step 5) for any renamed field (e.g. a
|
|
775
|
+
breakdown on `Category3`). Each call returns the viz `id` and a generated
|
|
776
|
+
`name` (e.g. `monthly_sales_trend_1782144120674`) — **capture both** for the
|
|
777
|
+
dashboard.
|
|
778
|
+
|
|
779
|
+
Control chart type via `marks.panes.type` and `visualSpecification.layout`:
|
|
780
|
+
- **Line / Time Series** — `type: "Line"`, `layout: "Vizql"`, date on columns
|
|
781
|
+
- **Bar (comparative, sorted)** — `type: "Bar"`, `layout: "Vizql"`, dim on
|
|
782
|
+
columns + measure on rows, `sortIntent: "MeasureDescending"`
|
|
783
|
+
- **Donut / Pie** — `type: "Donut"`, `layout: "Radial"`, measure on **`Angle`
|
|
784
|
+
encoding** (NOT rows), dimension on Color; use when dim has < 5 unique values
|
|
785
|
+
- **Scatter / Correlation** — `type: "Circle"`, `layout: "Vizql"`, X-measure on
|
|
786
|
+
columns, Y-measure on rows, point dimension on Color
|
|
787
|
+
- **Heatmap** — `type: "Square"`, `layout: "Vizql"`, two dims on rows/columns,
|
|
788
|
+
measure on Color encoding
|
|
789
|
+
- **Table** — `type: "Text"`, `layout: "Table"`, dims in `groups`, measures in `rows`
|
|
790
|
+
|
|
791
|
+
The shape and rules above cover the common charts. See
|
|
792
|
+
`references/viz-authoring.md` for the full chart-type decision matrix, field
|
|
793
|
+
binding rules, `marks.panes.type` values, and verified payloads for every
|
|
794
|
+
chart type, plus the fallback approach for unsupported chart types (Funnel,
|
|
795
|
+
Dot Matrix, Map, Flow/Sankey).
|
|
796
|
+
|
|
797
|
+
### Step 11 — Build the dashboard
|
|
798
|
+
|
|
799
|
+
`create_dashboard` (empty `widgets: {}`; seed one layout with an empty
|
|
800
|
+
`page_1` — full `style` + `columnCount`, since widgets get placed next; see
|
|
801
|
+
`dashboard-authoring.md` §1 — and always pass `minorVersion: -1` explicitly),
|
|
802
|
+
then place widgets on a **48-column grid**
|
|
803
|
+
(`rowHeight 20`, `maxWidth 1200`; half-width = colspan 24, quarter KPI = 12).
|
|
804
|
+
Place KPI widgets top/left per the narrative designed in Step 10 — see
|
|
805
|
+
`references/dashboard-design-principles.md` (MANDATORY, not optional).
|
|
806
|
+
|
|
807
|
+
- **Every widget kind (metric, visualization, text, or any other)** → hand
|
|
808
|
+
off to `tasks/edit-dashboard.md` (`edit_dashboard`'s atomic `upsert_*_widget`
|
|
809
|
+
+ `place_widget_on_page` batch — upsert → place, same batch or a later one).
|
|
810
|
+
Viz widgets need the `id`/`name` from Step 10's `create_visualization` call;
|
|
811
|
+
metric widgets need `parameters.metricOption.{sdmApiName, sdmId}` (`source`
|
|
812
|
+
optionally references the metric); text widgets
|
|
813
|
+
need a Quill delta (`parameters.content: [{ insert, attributes }]`). This
|
|
814
|
+
is a **staged draft by default** — send `minorVersion: -1`, then only
|
|
815
|
+
persist via `save_dashboard` after the user confirms.
|
|
816
|
+
- See `references/dashboard-authoring.md` for the complete verified
|
|
817
|
+
`create_dashboard` payload shapes, grid spec, and the `&`-encoding gotcha,
|
|
818
|
+
and `references/edit-dashboard.md` for the full `edit_dashboard` operation
|
|
819
|
+
table and widget-name-location rule.
|