@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.
- 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-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +0 -102
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Data Cloud SQL transforms — payloads and gotchas
|
|
2
|
+
|
|
3
|
+
Load this from `tasks/create-data-transform.md` (create **or** update). Not a
|
|
4
|
+
first hop.
|
|
5
|
+
|
|
6
|
+
Transforms run on **SparkInternal (SI)**, a subset of Data Cloud SQL. The
|
|
7
|
+
transform **creates its output object automatically** — do not pre-create the
|
|
8
|
+
target DLO/DMO.
|
|
9
|
+
|
|
10
|
+
## Contents
|
|
11
|
+
|
|
12
|
+
- Confirm before create / run / update → Confirmation
|
|
13
|
+
- Poll run history after run; `ACTIVE` ≠ rows ready → Run completion
|
|
14
|
+
- Fresh derived table (create) → Create
|
|
15
|
+
- Revise an existing transform-backed table → Update
|
|
16
|
+
- Nested `definitions` / `manifest` / `nodes.out` → Payload
|
|
17
|
+
- Flat body for create / update / validate (no envelope) → Payload
|
|
18
|
+
- BATCH↔DCSQL pairing, DMO `KQ_<pk>`, `fullRunFrequency` → Payload
|
|
19
|
+
- Output field names need `__c` → Payload
|
|
20
|
+
- SparkInternal unsupported ops → DCSQL
|
|
21
|
+
- Distinctive errors → Errors
|
|
22
|
+
|
|
23
|
+
## Confirmation
|
|
24
|
+
|
|
25
|
+
**NEVER call `create_data_transform`** or `update_data_transform` (or
|
|
26
|
+
`run_data_transform`) without explicit user approval. Show the generated SQL
|
|
27
|
+
plus a short `run_query` / `validate_data_transform` preview, state that a
|
|
28
|
+
new derived table will be created (or that `<outputApiName>` will be
|
|
29
|
+
redefined), and wait for an explicit "yes" / "proceed" / "go ahead". Silence
|
|
30
|
+
is not consent. Triggering the run is a **separate** confirmation gate.
|
|
31
|
+
|
|
32
|
+
Do not call `run_data_transform` while the transform is `PROCESSING` (it
|
|
33
|
+
fails). Lifecycle: `create → PROCESSING (~few min) → ACTIVE → run →
|
|
34
|
+
executes (typically 10–15 min)`. Creation becoming `ACTIVE` is not the
|
|
35
|
+
same as the output having rows — the run itself is the long step. After
|
|
36
|
+
`run_data_transform`, poll `get_data_transform_run_history` (see Run
|
|
37
|
+
completion).
|
|
38
|
+
|
|
39
|
+
## Run completion
|
|
40
|
+
|
|
41
|
+
After `run_data_transform`, poll `get_data_transform_run_history` until the
|
|
42
|
+
run succeeds or fails. Runs may be synchronous or asynchronous depending on
|
|
43
|
+
the transform's mode — do **not** infer completion from
|
|
44
|
+
`get_data_transform` status (`ACTIVE` means the definition is runnable, not
|
|
45
|
+
that this run finished or that the output has rows). If status looks stuck
|
|
46
|
+
(still running after the job should have finished), call
|
|
47
|
+
`refresh_data_transform_status` then poll history again.
|
|
48
|
+
|
|
49
|
+
## Create vs update
|
|
50
|
+
|
|
51
|
+
- **Create** when the target output object does **not** already exist.
|
|
52
|
+
- **Update** when the output is already transform-backed. Never create a
|
|
53
|
+
second transform for a table that already has one.
|
|
54
|
+
|
|
55
|
+
**Create workflow:** `browse_data_assets` for the exact `assetApiName` (with
|
|
56
|
+
`__dll` / `__dlm`) — if multiple match, STOP and ask → generate DCSQL →
|
|
57
|
+
`validate_data_transform` and/or `run_query(sql, rowLimit: 10)` → confirm →
|
|
58
|
+
create → `add_workspace_asset` on the new output → confirm →
|
|
59
|
+
`run_data_transform` → poll `get_data_transform_run_history`. Persist the
|
|
60
|
+
returned `id`. Batch cadence can be set
|
|
61
|
+
on create via `fullRunFrequency` — do not assume scheduling only happens
|
|
62
|
+
through `set_data_transform_schedule`.
|
|
63
|
+
|
|
64
|
+
**Update workflow:** `get_data_transform` first and resend the **entire**
|
|
65
|
+
structure (not a partial patch). Change only editable fields (typically
|
|
66
|
+
`compiled_code` and `fields` if SELECT columns changed). Copy every 🔒
|
|
67
|
+
immutable field byte-for-byte. Validate → confirm → update. Skip add-to-
|
|
68
|
+
workspace. Confirm before run — the output stays OLD until the transform
|
|
69
|
+
runs. After run, poll `get_data_transform_run_history` (see Run completion).
|
|
70
|
+
|
|
71
|
+
**ALWAYS append the SAME 4-digit random number** (1000–9999) to all three API
|
|
72
|
+
names in one transform: `transform_<n>`, `definition_<n>`, and
|
|
73
|
+
`output_<n>__dll` (or `__dlm` for a DMO). Example: `my_transform_7521`,
|
|
74
|
+
`my_definition_7521`, `my_output_7521__dll`. Avoids "Duplicate name" errors.
|
|
75
|
+
|
|
76
|
+
## Payload (reference shape)
|
|
77
|
+
|
|
78
|
+
`outputDataObjects` is **required** — omitting it → *"Target Dlo not found"*.
|
|
79
|
+
`manifest.nodes.out.relation_name` must === `outputDataObjects[0].name`.
|
|
80
|
+
`fields` count/names/types must match SELECT columns. At least one
|
|
81
|
+
`fields[].isPrimaryKey` must be `true`. Pair top-level `type` with
|
|
82
|
+
`definitions[].type`: **BATCH → `DCSQL`**, **STREAMING → `SQL`**. The
|
|
83
|
+
schema otherwise allows `DCSQL` / `SQL` / `STL` as a free choice — a
|
|
84
|
+
mismatched pair fails. DMO outputs need `dataSpaceName` and
|
|
85
|
+
`type: "dataModelObject"` with a `__dlm` name, plus a key-qualifier:
|
|
86
|
+
each PK carries `"keyQualifierField": "KQ_<pk>"` and you add an explicit
|
|
87
|
+
`KQ_<pk>` field (same suffix as the PK name).
|
|
88
|
+
|
|
89
|
+
Date SQL (`cast(x AS date)`) maps to field type **`DateOnly`** (not `Date`).
|
|
90
|
+
|
|
91
|
+
**Every output field name must be `__c`-suffixed** (same convention as
|
|
92
|
+
DLO/DMO ingest fields) — SELECT column aliases in `compiled_code` and the
|
|
93
|
+
matching `fields[].name` entries all need the suffix, e.g. `status__c`,
|
|
94
|
+
`order_count__c`, not `status`, `order_count`. `validate_data_transform`
|
|
95
|
+
does **not** reject a bare alias — it silently derives an `__c`-suffixed
|
|
96
|
+
field in the schema it returns without warning. If you then create/update
|
|
97
|
+
with the bare alias still in `compiled_code`, the transform creates fine
|
|
98
|
+
and goes `ACTIVE`, but `run_data_transform` fails post-hoc with `"target
|
|
99
|
+
object ... doesn't have the required field <bare_alias>"` because the
|
|
100
|
+
runtime write binds by the literal SQL alias, which no longer matches the
|
|
101
|
+
silently-renamed backing field. Always write `__c`-suffixed aliases
|
|
102
|
+
yourself in both `compiled_code` and `fields[]` — do not rely on
|
|
103
|
+
`validate_data_transform`'s response to catch a bare one.
|
|
104
|
+
|
|
105
|
+
`create_data_transform`, `update_data_transform`, and
|
|
106
|
+
`validate_data_transform` all take this **same flat body** — `name`,
|
|
107
|
+
`type`, `definitions` at the top level. Do **not** wrap it in
|
|
108
|
+
`dataTransformObject` or `dataTransform`.
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"name": "my_transform_1234",
|
|
113
|
+
"label": "My Transform",
|
|
114
|
+
"type": "BATCH",
|
|
115
|
+
"dataSpaceName": "default",
|
|
116
|
+
"definitions": [{
|
|
117
|
+
"name": "my_definition_1234",
|
|
118
|
+
"label": "Definition Label",
|
|
119
|
+
"type": "DCSQL",
|
|
120
|
+
"version": "65.0",
|
|
121
|
+
"outputDataObjects": [{
|
|
122
|
+
"name": "my_output_1234__dll",
|
|
123
|
+
"label": "Output Label",
|
|
124
|
+
"type": "dataLakeObject",
|
|
125
|
+
"category": "Other",
|
|
126
|
+
"recordModifiedFieldName": "",
|
|
127
|
+
"fields": [
|
|
128
|
+
{ "name": "id__c", "label": "ID", "type": "Text", "isPrimaryKey": true },
|
|
129
|
+
{ "name": "amount__c", "label": "Amount", "type": "Number", "isPrimaryKey": false }
|
|
130
|
+
]
|
|
131
|
+
}],
|
|
132
|
+
"manifest": {
|
|
133
|
+
"nodes": {
|
|
134
|
+
"out": {
|
|
135
|
+
"name": "out",
|
|
136
|
+
"relation_name": "my_output_1234__dll",
|
|
137
|
+
"config": { "materialized": "table" },
|
|
138
|
+
"compiled_code": "SELECT id__c, cast(amount__c AS numeric) AS \"amount__c\" FROM \"source__dll\""
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"sources": { "src1": { "relation_name": "source__dll" } }
|
|
142
|
+
}
|
|
143
|
+
}]
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
DMO output fields (instead of the DLO pair above) — PK + explicit qualifier:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
[
|
|
151
|
+
{ "name": "id__c", "label": "ID", "type": "Text", "isPrimaryKey": true, "keyQualifierField": "KQ_id" },
|
|
152
|
+
{ "name": "KQ_id", "label": "KQ id", "type": "Text", "isPrimaryKey": false }
|
|
153
|
+
]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**🔒 immutable after create** (copy exactly on update): `name`, `type`,
|
|
157
|
+
`definitions[0].name`, `definitions[0].type`, `outputDataObjects[0].name`,
|
|
158
|
+
`outputDataObjects[0].type`, `outputDataObjects[0].recordModifiedFieldName`,
|
|
159
|
+
`manifest.nodes.out.name` (always `"out"`), `manifest.nodes.out.relation_name`
|
|
160
|
+
(**`relation_name` must ===** `outputDataObjects[0].name`),
|
|
161
|
+
`manifest.nodes.out.config` (`{"materialized":"table"}`).
|
|
162
|
+
**Changing any 🔒 field → 400.** Editable: labels, description, DMO
|
|
163
|
+
`dataSpaceName`, `category`, `fields[]`, `compiled_code`, `manifest.sources`.
|
|
164
|
+
|
|
165
|
+
## Update timeout
|
|
166
|
+
|
|
167
|
+
The update API may time out while the update **succeeds** on the server.
|
|
168
|
+
**NEVER blindly retry.** On `TIMEOUT` / 504 / "timed out":
|
|
169
|
+
|
|
170
|
+
1. `get_data_transform`.
|
|
171
|
+
2. Compare `definitions[0].manifest.nodes.out.compiled_code` to the new SQL.
|
|
172
|
+
3. If it **matches** → succeeded; do not retry.
|
|
173
|
+
4. If it **differs** → did not apply; retry once.
|
|
174
|
+
|
|
175
|
+
## DCSQL (SparkInternal)
|
|
176
|
+
|
|
177
|
+
This SI subset is **only** for `create_data_transform` / `update_data_transform`.
|
|
178
|
+
Do not carry it over to `run_query` (that is Hyper SQL / full SELECT — see
|
|
179
|
+
`tasks/query-model.md`).
|
|
180
|
+
|
|
181
|
+
Validate with `run_query(sql, rowLimit: 10)` for a fast preview; SI
|
|
182
|
+
compatibility is checked inside create/update. On failure, swap the
|
|
183
|
+
unsupported op and retry.
|
|
184
|
+
|
|
185
|
+
Compressed substitutions (do not use the left-hand ops):
|
|
186
|
+
|
|
187
|
+
| Unsupported | Use instead |
|
|
188
|
+
|---|---|
|
|
189
|
+
| `varpop(x)` / `varsamp(x)` | `power(stddevpop(x),2)` / `power(stddevsamp(x),2)` |
|
|
190
|
+
| `regexpreplace` / `regexpsubstr` | `replace()` + `substring()` |
|
|
191
|
+
| `position(x in y)` | `arrayposition(y,x)` (arrays) or compute manually |
|
|
192
|
+
| `age(d1,d2)` | `datediff(unit, d2, d1)` |
|
|
193
|
+
| `tochar(v,fmt)` | `cast(v AS varchar)` |
|
|
194
|
+
| `val <=all (subq)` / `>=all` | `val <= (select min(...) …)` / `>= (select max(...) …)` |
|
|
195
|
+
|
|
196
|
+
Also unsupported: `chr`, `overlay`, `similarto`, `trunc`, `sequence`,
|
|
197
|
+
`regexp*`, fiscal `extract*`, casts `date←timestamp` / `integer←bigint` /
|
|
198
|
+
`timestamp←varchar`. Avoid correlated subqueries and deeply nested `CASE`.
|
|
199
|
+
|
|
200
|
+
## Errors
|
|
201
|
+
|
|
202
|
+
| Error | Fix |
|
|
203
|
+
|---|---|
|
|
204
|
+
| `Target Dlo not found` | Include `outputDataObjects` |
|
|
205
|
+
| `Schema mismatch` | `fields` must match SELECT columns exactly |
|
|
206
|
+
| `At least one field must be primary key` | `isPrimaryKey: true` on ≥1 field |
|
|
207
|
+
| `Source relation not found` | exact `assetApiName` from `browse_data_assets`, with `__dll`/`__dlm` |
|
|
208
|
+
| `relation_name` mismatch | `manifest.nodes.out.relation_name` === `outputDataObjects[0].name` |
|
|
209
|
+
| `DMO requires dataSpaceName` | `"dataSpaceName": "default"` at transform level |
|
|
210
|
+
| `target object ... doesn't have the required field <name>` (on run, not create/validate) | Bare (non-`__c`) SELECT alias — re-suffix it `<name>__c` in both `compiled_code` and `fields[]`, then update |
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
# Data Understanding (profile before you author)
|
|
2
|
+
|
|
3
|
+
Load this **after** the data is queryable — either after you've profiled and
|
|
4
|
+
ingested a flat file (`large-flat-file-handling.md`, then the Step 0 ingest), or
|
|
5
|
+
over an already-built model — and **before** you choose
|
|
6
|
+
calculated fields, metrics, or charts. Proving the data has *rows and joins* shows
|
|
7
|
+
it's *queryable*; this step proves you understand what the data *means* so the
|
|
8
|
+
analysis and the chart actually answer the user's question. The single most common
|
|
9
|
+
quality failure after "empty dashboard" is a **populated dashboard that charts the
|
|
10
|
+
wrong thing** — a count of an ID treated as a measure, a sum of a ratio, a
|
|
11
|
+
200-category bar chart, a line over a load timestamp.
|
|
12
|
+
|
|
13
|
+
> Tableau Desktop profiles a published datasource or extract; Tableau Next
|
|
14
|
+
> profiles the **SDM over Data Cloud** — the grain, types, cardinality, and date
|
|
15
|
+
> roles all come from `list_semantic_model_*` + `run_semantic_query` /
|
|
16
|
+
> `run_query`, never from XML metadata. The *questions* below are
|
|
17
|
+
> product-agnostic; the *tools* are Next-native.
|
|
18
|
+
|
|
19
|
+
> **Where the build happens.** This file is the bridge between *understanding* the
|
|
20
|
+
> data and *building* the SDM. The actual model/metric/chart construction (the
|
|
21
|
+
> **Step N** references below — Step 5 read-back, Step 8 metric creation, Step 10
|
|
22
|
+
> charting — and the **`viz-authoring.md` / `dashboard-authoring.md` /
|
|
23
|
+
> `semantic-query-and-enrichment.md`**
|
|
24
|
+
> reference files) is the workflow in this skill's `SKILL.md`. The
|
|
25
|
+
> **"From data understanding to the SDM"** map at the end of this file translates
|
|
26
|
+
> each profiling finding into the exact authoring choice it constrains there.
|
|
27
|
+
|
|
28
|
+
Before any of the five, run the **elicitation gate** (below): if a field, segment,
|
|
29
|
+
value, or requested metric you need is load-bearing but its *meaning* is ambiguous —
|
|
30
|
+
on any dataset, modeled or flat — STOP and ask the user with your best guess
|
|
31
|
+
attached. Profiling tells you the data's shape; it cannot tell you what a column
|
|
32
|
+
*means*, and meaning is what the analysis depends on.
|
|
33
|
+
|
|
34
|
+
## Contents
|
|
35
|
+
|
|
36
|
+
- The five things to know before authoring
|
|
37
|
+
- Snapshot / panel grain — dedup to latest before aggregating
|
|
38
|
+
- Window flags & counts → calc measures (the `_7D/_30D/_90D` grammar)
|
|
39
|
+
- Profiling queries (cheap, run before authoring)
|
|
40
|
+
- Elicitation gate — ask what a load-bearing field MEANS (hard stop, any dataset)
|
|
41
|
+
- From data shape to the right question
|
|
42
|
+
- From data understanding to the SDM (the handoff)
|
|
43
|
+
- Common mistakes
|
|
44
|
+
|
|
45
|
+
## The five things to know before authoring
|
|
46
|
+
|
|
47
|
+
| # | Question | How to find it | Why it matters |
|
|
48
|
+
|---|---|---|---|
|
|
49
|
+
| 1 | **Grain** — what is one row? | `run_query SELECT COUNT(*)` vs `COUNT(DISTINCT <pk>)` on the fact object; read object labels | Determines whether a "count" is meaningful and what a measure sums to. One row per order ≠ one row per line item. **A relationship can change the grain** — a 1:N join fans the fact out, so re-profile after adding one (mistake #7). |
|
|
50
|
+
| 2 | **Field roles** — dim / measure / date | `list_semantic_model_dimensions`, `list_semantic_model_measures`; check `dataType` | A numeric ID is a *dimension*, not a measure. A `Date`/`DateTime` dim gates timeseries + metrics. |
|
|
51
|
+
| 3 | **Cardinality** — distinct values per dimension | `run_semantic_query` grouping by the dim with `limit_options` + a count, or `run_query SELECT COUNT(DISTINCT col)` | Drives chart choice: 2–7 → bar/pie ok; 8–20 → horizontal bar; 20+ → top-N + "Other", treemap, or a different cut. |
|
|
52
|
+
| 4 | **Measure additivity** — sum-safe vs ratio | inspect the measure's meaning + `aggregationType`; ratios/rates/percentages are NOT additive | Summing a margin %, a rate, or an average across groups is wrong. Additive (amount, count, quantity) → `Sum`; ratio → `Average`/`UserAgg` or recompute from numerator/denominator. |
|
|
53
|
+
| 5 | **Date roles** — event date vs plumbing | `list_semantic_model_dimensions` → `dataType: "Date"`; read the name | Only a **business event date** (created/close/order/activity) anchors a timeseries or metric. `cdp_sys_PartitionDate`, `*_SourceVersion`, ingestion timestamps are plumbing — never the time axis. |
|
|
54
|
+
|
|
55
|
+
## Snapshot / panel grain — dedup to latest before aggregating
|
|
56
|
+
|
|
57
|
+
A frequent shape (especially from wide enterprise extracts — see
|
|
58
|
+
`large-flat-file-handling.md`) is a **periodic snapshot**: a date column (`SNAP_DT`,
|
|
59
|
+
`SNAPSHOT_DATE`, `AS_OF`, `PARTITION_DATE`) repeated for every entity, so the
|
|
60
|
+
grain is **entity × snapshot-date**, not entity. Detect it: if
|
|
61
|
+
`COUNT(DISTINCT snapshot_date) × COUNT(DISTINCT entity_id) ≈ COUNT(*)`, it's a
|
|
62
|
+
panel. This is a **grain question (row 1)** with a sharp consequence — most exec
|
|
63
|
+
KPIs want *current state per entity*, so you must **dedup to the latest snapshot
|
|
64
|
+
per entity** (`max(snapshot_date)` per id) before any count or %. Aggregating the
|
|
65
|
+
raw panel multiplies every count by the number of snapshots.
|
|
66
|
+
|
|
67
|
+
The identity is **necessary, not sufficient**: a once-per-period **transaction fact**
|
|
68
|
+
(one row per order/invoice per period) matches it too, and deduping *that* to
|
|
69
|
+
latest-per-entity would discard real events. Confirm it's an as-of snapshot first —
|
|
70
|
+
the date should be a stamp **shared across the population** (≈ one row per entity per
|
|
71
|
+
date), not sparse per-entity event dates — and profile rows-per-snapshot-date so a
|
|
72
|
+
**short final load** doesn't dedup you onto a truncated population. Both discriminators
|
|
73
|
+
are worked out in `large-flat-file-handling.md §4`.
|
|
74
|
+
|
|
75
|
+
- The deduped row set is the **population** every headline % divides by — verify
|
|
76
|
+
it (`large-flat-file-handling.md §5`) before authoring.
|
|
77
|
+
- Use `max` *per entity*, not a global `WHERE date = max(date)` — entities that
|
|
78
|
+
dropped out earlier still have a valid "latest" row.
|
|
79
|
+
- Expose the deduped grain to the SDM **in the model**, with a **Text latest-flag
|
|
80
|
+
calculated dimension + a metric filter** (`large-flat-file-handling.md §4`): a
|
|
81
|
+
constant `As_Of_Date` anchor, a `Text` `Is_Latest_Snapshot` flag (`IF [Obj].[ds] =
|
|
82
|
+
DATE("<max>") THEN "Y" ELSE "N" END`), then `filters: [Is_Latest_Snapshot = "Y"]`
|
|
83
|
+
on every snapshot metric. Author the flag as a `Text` "Y"/"N" value so the metric
|
|
84
|
+
filters on a discrete scalar, not a raw `Date` comparison
|
|
85
|
+
(`large-flat-file-handling.md §4`).
|
|
86
|
+
|
|
87
|
+
## Window flags & counts → calc measures (the `_7D/_30D/_90D` grammar)
|
|
88
|
+
|
|
89
|
+
Wide behavioral extracts encode trailing-window activity as **per-row flags and
|
|
90
|
+
counts**, not as raw events: `X_30D` (did X in the last 30 days, 0/1),
|
|
91
|
+
`X_TOT_30D` (count of X in 30 days), `X_DISTINCT_30D` (distinct count). After
|
|
92
|
+
deduping to latest (above), these become straightforward derived measures over
|
|
93
|
+
the population:
|
|
94
|
+
|
|
95
|
+
| Want | From | Calc |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| **% of population who did X (30D)** | `X_30D` flag | `SUM(flag) / COUNT(entity)` → a ratio calc measure (`UserAgg`), or a metric counting truthy flags ÷ population |
|
|
98
|
+
| **avg events per entity** | `X_TOT_30D` | `SUM(X_TOT_30D) / COUNT(entity)` |
|
|
99
|
+
| **avg events per active entity** | `X_TOT_30D`, `X_30D` | `SUM(X_TOT_30D) / SUM(X_30D flag)` |
|
|
100
|
+
| **frequency bucket** (e.g. daily-equiv) | `X_TOT_30D` | a calc **dimension** bucketing the count (`>=60` → "≥2/day", `30–59` → "~1/day") — confirm thresholds with the user |
|
|
101
|
+
| **segment membership** | one or more flags | a calc dimension; **if the segment has no single backing column, ASK the user for its definition** (`large-flat-file-handling.md §3`) before encoding it |
|
|
102
|
+
|
|
103
|
+
Per the skill's default, build these **both** as calc measures/dimensions (the
|
|
104
|
+
point-in-time KPI on the latest snapshot) **and**, where the panel supports it, as
|
|
105
|
+
metrics anchored on the snapshot date so the trend across snapshots is available.
|
|
106
|
+
A percentage is a **ratio → not additive** (row 4): use `UserAgg` / recompute from
|
|
107
|
+
numerator and denominator, never `Sum` across groups.
|
|
108
|
+
|
|
109
|
+
> **Anchoring a snapshot metric: a constant as-of date, never a per-row activity
|
|
110
|
+
> timestamp.** A metric's `timeDimensionReference` is **required** (you cannot omit
|
|
111
|
+
> it to make the metric "non-temporal"), and the KPI card renders the value at the
|
|
112
|
+
> **latest time period**. A deduped snapshot is *one* period's worth of truth — the
|
|
113
|
+
> as-of moment — so every row the metric counts must share **one** anchor date. If
|
|
114
|
+
> you instead anchor on a per-row event/activity attribute (a `LAST_*_TS`, "last
|
|
115
|
+
> login", "last export"), the card fractures the population: rows scatter across
|
|
116
|
+
> the months of their individual timestamps, rows with a `null` timestamp orphan
|
|
117
|
+
> into a null bucket, and the card displays only the latest month's slice — silently
|
|
118
|
+
> under-reporting the true total. This is the snapshot form of mistake #4 below, and
|
|
119
|
+
> it is the single most common way a snapshot KPI ships *plausibly* wrong (a number
|
|
120
|
+
> in the right ballpark, just not the real total).
|
|
121
|
+
>
|
|
122
|
+
> A single extract often has **no snapshot-date column at all** — there is no `SNAP_DT` /
|
|
123
|
+
> `AS_OF` to anchor on. **Create one as a constant** calc dimension, then point
|
|
124
|
+
> **every** snapshot metric's `timeDimensionReference` at it:
|
|
125
|
+
>
|
|
126
|
+
> ```jsonc
|
|
127
|
+
> // 1. the constant as-of dimension — add_semantic_model_calculated_dimension
|
|
128
|
+
> // substitute the snapshot's ACTUAL as-of date (the extract date, or the
|
|
129
|
+
> // max(snapshot_date) you deduped to, or today if that's the as-of) for the literal
|
|
130
|
+
> { "apiName": "As_Of_Date", "label": "As Of Date",
|
|
131
|
+
> "expression": "DATE(\"2026-06-23\")", "dataType": "Date",
|
|
132
|
+
> "displayCategory": "Continuous" }
|
|
133
|
+
> // 2. EVERY snapshot metric anchors on it — add/update_semantic_model_metric
|
|
134
|
+
> "timeDimensionReference": { "calculatedFieldApiName": "As_Of_Date" }
|
|
135
|
+
> ```
|
|
136
|
+
>
|
|
137
|
+
> The whole population then collapses into a single time bucket and the card shows
|
|
138
|
+
> the real headline (population count, flag-sum, etc.). **Point `measurementReference`
|
|
139
|
+
> at a fully-populated numeric measure with `aggregationType: "Count"`** (Count counts
|
|
140
|
+
> non-null values, so a gappy field under-reports) — **never at the entity text Primary Key**
|
|
141
|
+
> (e.g. `Employee_ID`), which the API rejects (`field … was not found in the model`);
|
|
142
|
+
> the Primary Key is still the `identifyingDimension` (see Step 8).
|
|
143
|
+
>
|
|
144
|
+
> **This constant as-of date is NOT the "junk/plumbing date" the Step 8 rule warns
|
|
145
|
+
> against.** That rule forbids a load/system timestamp (`cdp_sys_*`, `*_SourceVersion`)
|
|
146
|
+
> standing in for a real event — and a per-row activity timestamp on a snapshot. For a
|
|
147
|
+
> single deduped snapshot there is no event date to use; the constant as-of date IS the
|
|
148
|
+
> business-meaningful anchor (it states "as of `<date>`, the population is N"). Keep the
|
|
149
|
+
> four anchor cases distinct: a **real event date** → event streams; a **genuinely
|
|
150
|
+
> differing snapshot date** → multi-snapshot trends; a **constant as-of date** → a
|
|
151
|
+
> single snapshot; a **plumbing/load timestamp** → always wrong. Only with multiple
|
|
152
|
+
> snapshots does anchoring on the snapshot date itself yield a real trend; on one
|
|
153
|
+
> snapshot, the constant is correct.
|
|
154
|
+
>
|
|
155
|
+
> After authoring, verify (`large-flat-file-handling.md §5`): query the metric at
|
|
156
|
+
> `Month` grain and confirm it returns **one** bucket equal to the known total, not
|
|
157
|
+
> a scatter across months plus a null bucket.
|
|
158
|
+
|
|
159
|
+
> **These calc fields are queryable, but the chart tools want native fields.**
|
|
160
|
+
> Everything in the table above is a **calculated** measure/dimension —
|
|
161
|
+
> `run_semantic_query` reads them, and a calc *measure* can back a **metric** (hence a
|
|
162
|
+
> KPI card). But the chart tools (`create_timeseries_viz` /
|
|
163
|
+
> `create_comparative_viz` / `create_relationship_viz`, `viz-authoring.md`)
|
|
164
|
+
> place **native** object fields on their shelves — a calc dimension/measure does not
|
|
165
|
+
> chart directly. So the natural home for these window-flag numbers is **metric
|
|
166
|
+
> widgets / KPI cards**, not bars. Plan for that from the start: a snapshot/adoption
|
|
167
|
+
> file becomes a **KPI-card dashboard** (one card per rate/count), with a chart only
|
|
168
|
+
> where a **native** field carries it.
|
|
169
|
+
|
|
170
|
+
### Materialize segments as native columns (when you truly need to chart them)
|
|
171
|
+
|
|
172
|
+
If a derived **segment** (an adoption bucket, a frequency tier, a window flag) must
|
|
173
|
+
appear **on a chart axis or Color** — not just as a KPI number — it has to be a
|
|
174
|
+
**native column on the data object**, because a calc dimension won't bind. Create
|
|
175
|
+
it at **DMO-create / DLO→DMO mapping time** (a mapped/derived field, or a
|
|
176
|
+
transform that writes the bucket as a real column), so by Step 5 it reads back as a
|
|
177
|
+
native dimension you can put on a shelf. Decide this **early**, while modelling — you
|
|
178
|
+
cannot retrofit a native column after the chart fails. Default, though, is *don't*:
|
|
179
|
+
report segments as KPI cards (above) and reserve native-column materialization for a
|
|
180
|
+
segment that genuinely earns a bar. **If the segment has no single backing column
|
|
181
|
+
and no clear definition, ASK the user before encoding it** (`large-flat-file-handling.md §3`).
|
|
182
|
+
|
|
183
|
+
## Profiling queries (cheap, run before authoring)
|
|
184
|
+
|
|
185
|
+
Row count and grain check (raw `run_query`, source-suffixed name):
|
|
186
|
+
|
|
187
|
+
```sql
|
|
188
|
+
SELECT COUNT(*) AS rows, COUNT(DISTINCT order_id) AS distinct_orders FROM orders__dll
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Cardinality + distribution of a candidate breakdown dimension (semantic query —
|
|
192
|
+
use SDM data-object apiName + suffixed field apiName, see
|
|
193
|
+
`semantic-query-and-enrichment.md`):
|
|
194
|
+
|
|
195
|
+
```jsonc
|
|
196
|
+
{ "fields": [
|
|
197
|
+
{ "expression": { "table_field": { "name": "Category3", "table_name": "Orders" } },
|
|
198
|
+
"alias": "Orders.Category3", "grouping": "ROW_GROUPING" },
|
|
199
|
+
{ "expression": { "table_field": { "name": "Sales7", "table_name": "Orders" } },
|
|
200
|
+
"alias": "Orders.Sales7", "semantic_aggregation_method": "SEMANTIC_AGGREGATION_METHOD_COUNT" } ],
|
|
201
|
+
"options": { "limit_options": { "limit": 50 },
|
|
202
|
+
"sort_orders": [ { "simple_sort_order": { "sort_by_field_alias": "Orders.Sales7", "sorting_order": "DESC" } } ] } }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Read the number of returned groups = cardinality; read the spread to spot a
|
|
206
|
+
dominant category or a long tail (the "Other" candidates). A `null` grouping row
|
|
207
|
+
is the blank/unmatched bucket — note it; it often signals a data-quality gap
|
|
208
|
+
worth surfacing to the user rather than charting silently.
|
|
209
|
+
|
|
210
|
+
Date-range / freshness check on a candidate time anchor:
|
|
211
|
+
|
|
212
|
+
```sql
|
|
213
|
+
SELECT MIN(order_date) AS first, MAX(order_date) AS last, COUNT(DISTINCT order_date) AS n_dates FROM orders__dll
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
A single distinct date (or first==last) means there is no trend to plot — a
|
|
217
|
+
timeseries would be a single point; pick a comparison chart instead.
|
|
218
|
+
|
|
219
|
+
## Elicitation gate — ask what a load-bearing field MEANS (hard stop, any dataset)
|
|
220
|
+
|
|
221
|
+
Profiling tells you the *shape* of the data (grain, type, cardinality, dates). It
|
|
222
|
+
does **not** tell you the *business meaning* of a field, a segment, or a value —
|
|
223
|
+
and meaning is what the analysis turns on. **Whenever a field, dimension value,
|
|
224
|
+
segment, or requested metric is load-bearing for the user's question but its
|
|
225
|
+
meaning cannot be resolved from the schema alone, STOP and ask before authoring.**
|
|
226
|
+
This gate fires for ANY dataset — modeled SDM or flat file, large or small,
|
|
227
|
+
cryptically or friendly named. Ambiguity is a property of the request against the
|
|
228
|
+
data, not of the file's size or naming. The four triggers:
|
|
229
|
+
|
|
230
|
+
- **Ambiguous meaning** — a field/dimension/value whose definition is unclear and
|
|
231
|
+
the analysis depends on it: an opaque numeric measure of unknown derivation
|
|
232
|
+
(`health_index`, `engagement_score`), or a low-cardinality dimension whose values
|
|
233
|
+
are codes you can't decode (`stage` = `S2`/`CW`/`CL`, `tier` = `A`/`B`/`C`). It
|
|
234
|
+
profiles cleanly (tidy cardinality, sum-safe type) and will chart silently as
|
|
235
|
+
opaque labels — that is exactly the trap. Ask, e.g.: *"`stage` has values S2/CW/CL
|
|
236
|
+
— what do those mean and what order? My guess: Stage 2 / Closed-Won / Closed-Lost."*
|
|
237
|
+
or *"What does `health_index` measure and how is it derived — is it sum-safe or a
|
|
238
|
+
per-entity ratio I should average?"*
|
|
239
|
+
- **Derived segment with no single backing column** — "high-value account",
|
|
240
|
+
"at-risk customer", "engaged account", "active user": ask which columns/thresholds
|
|
241
|
+
define it, with your best guess attached: *"There's no single `at_risk` column. How
|
|
242
|
+
do you define at-risk — e.g. `renewal_date < 90d AND health < 0.5`?"*
|
|
243
|
+
- **Requested-but-absent metric** — the user named something with no backing field.
|
|
244
|
+
Say so and ask; **never fabricate a value**: *"There's no column for churn rate in
|
|
245
|
+
this model. Should I show it as N/A, drop it, or derive it from a column I'm not
|
|
246
|
+
recognizing?"* Surface an explicit 0 / "not present" only with the user's say-so.
|
|
247
|
+
- **Clean-but-conventional measure — units, sign, currency, window basis.** The most
|
|
248
|
+
dangerous field is the one that profiles *perfectly*: a tidy numeric `Amount`,
|
|
249
|
+
sum-safe type, no nulls — and is silently off by 100×, or backwards, or mixed. A
|
|
250
|
+
field's shape can't tell you its **convention**, and a `SUM`/`AVG` inherits the
|
|
251
|
+
convention whether or not you checked it. Before aggregating a money/quantity/rate
|
|
252
|
+
measure, resolve four conventions that don't show up in the schema — ask only the
|
|
253
|
+
ones the analysis actually turns on, best guess attached:
|
|
254
|
+
- **Units & scale** — *"Is `revenue` in dollars or cents? Is `margin` a fraction
|
|
255
|
+
(0–1) or a percent (0–100)?"* Cents-vs-dollars or 0–1-vs-0–100 is a 100× error
|
|
256
|
+
that charts cleanly.
|
|
257
|
+
- **Sign convention** — *"Do returns/credits/adjustments carry a negative sign in
|
|
258
|
+
`amount`, or is direction in a separate `type` column?"* If refunds are negative,
|
|
259
|
+
`SUM(amount)` is net, not gross — and a naive `SUM` of a mixed-sign column silently
|
|
260
|
+
nets them when the user wanted gross (or vice versa).
|
|
261
|
+
- **Single currency** — *"Is `amount` all one currency, or is there a `currency_code`
|
|
262
|
+
I should split/convert by?"* Summing mixed currencies into one bar is meaningless.
|
|
263
|
+
- **Trailing-window basis** — for `_7D/_30D/_90D`-style fields (see
|
|
264
|
+
`large-flat-file-handling.md §2`): *"Is `LOGIN_30D` a trailing-30-day window as of
|
|
265
|
+
the snapshot, a calendar-month count, or life-to-date?"* The window basis decides
|
|
266
|
+
whether two `_30D` fields are even comparable, and never appears in the type.
|
|
267
|
+
|
|
268
|
+
Ask **targeted, bounded** questions — only about the residue that blocks authoring,
|
|
269
|
+
never "what does everything mean?" — and **propose your best guess** so the user
|
|
270
|
+
confirms or corrects in one line. **Never silently infer a load-bearing definition
|
|
271
|
+
and proceed.** Ten seconds of confirmation prevents a confidently-wrong dashboard.
|
|
272
|
+
|
|
273
|
+
**The gate is not one-and-done — re-fire it when profiling surprises you.** You run
|
|
274
|
+
the gate *before* profiling on what the schema reveals, but profiling itself turns
|
|
275
|
+
up meaning questions the schema hid: a column that's **60% null**, a dimension with
|
|
276
|
+
an enormous **`null`/`Unknown` bucket**, a measure whose range is implausible
|
|
277
|
+
(negatives where you expected counts, a max 100× the others), a date that's a single
|
|
278
|
+
value. Each of these **re-opens the meaning question** — a huge null bucket isn't
|
|
279
|
+
just "a data-quality note to surface," it may mean the field means something other
|
|
280
|
+
than you assumed (optional-by-design, a sentinel, a default), and charting past it
|
|
281
|
+
encodes the wrong assumption. When a profiling result contradicts your decode, stop
|
|
282
|
+
and re-ask (with your best guess) before you build on it.
|
|
283
|
+
|
|
284
|
+
**No human available (unattended / eval harness): do not block.** Record the exact
|
|
285
|
+
question and your best-guess assumption verbatim in the run notes / source line,
|
|
286
|
+
proceed on that stated assumption, and flag the resulting field/metric as
|
|
287
|
+
assumption-based so it can be reviewed — the same "surface it, state it, proceed"
|
|
288
|
+
pattern used for the dedup/source line, never a silent guess and never a hard halt.
|
|
289
|
+
|
|
290
|
+
The wide-extract instance of this same gate (decoding a column-naming grammar, then
|
|
291
|
+
asking about the window-flag / threshold / snapshot residue) is worked out in
|
|
292
|
+
`large-flat-file-handling.md §3`; that is one shape of this gate, not its only home.
|
|
293
|
+
|
|
294
|
+
## From data shape to the right question
|
|
295
|
+
|
|
296
|
+
Profiling tells you which analytical questions the data can actually answer.
|
|
297
|
+
Map the user's intent to a question category, then to the matching chart tool in
|
|
298
|
+
`viz-authoring.md`:
|
|
299
|
+
|
|
300
|
+
- **One measure across a low-cardinality dimension** → comparison (**Bar**,
|
|
301
|
+
`create_comparative_viz`).
|
|
302
|
+
- **One measure over a real event date** → trend (**Line**, `create_timeseries_viz`).
|
|
303
|
+
- **Two measures per entity** → relationship (**Circle** / scatter,
|
|
304
|
+
`create_relationship_viz`).
|
|
305
|
+
- **Spread/shape of one measure** (deal sizes, response times) → distribution. Next's
|
|
306
|
+
chart tools have no native histogram/box mark, so render it as a **binned bar**:
|
|
307
|
+
bucket the measure into a calc/native dimension and chart counts per bucket with
|
|
308
|
+
`create_comparative_viz`.
|
|
309
|
+
- **A part-to-whole of one measure across 2–5 segments** → composition (**Donut**)
|
|
310
|
+
*only if the segment is a native dimension*; if the split is a calc dimension,
|
|
311
|
+
report it as KPI cards instead (see below).
|
|
312
|
+
- **A single tracked headline number anchored on an event date** → a **metric** (Step 8), not a chart.
|
|
313
|
+
- **"How many adopting / how intensely" over a deduped snapshot (adoption/health)**
|
|
314
|
+
→ a **KPI-card dashboard**, not charts: the rates and counts are calc
|
|
315
|
+
measures/flags that don't chart directly, so present them as metric widgets laid out
|
|
316
|
+
per `dashboard-authoring.md`. Add a trend strip only across multiple snapshots, on
|
|
317
|
+
a native date.
|
|
318
|
+
|
|
319
|
+
(Each chart tool and its payload shape — `create_*_viz` — is in
|
|
320
|
+
`viz-authoring.md`.)
|
|
321
|
+
|
|
322
|
+
If the data cannot answer the literal request (no event date for a "trend over
|
|
323
|
+
time", no second measure for a "correlation", every value in one category),
|
|
324
|
+
**say so and offer the closest answerable question** — do not silently
|
|
325
|
+
substitute a chart that misrepresents the data.
|
|
326
|
+
|
|
327
|
+
## From data understanding to the SDM (the handoff)
|
|
328
|
+
|
|
329
|
+
Profiling is not an end in itself — every finding above **constrains a specific
|
|
330
|
+
authoring decision** in the SDM build (this skill's `SKILL.md` workflow).
|
|
331
|
+
Carry the findings forward as a checklist; each row is a profiling result on the left
|
|
332
|
+
and the exact build choice it dictates on the right. Skipping this is how a correctly
|
|
333
|
+
*profiled* dataset still ships a wrong *model*.
|
|
334
|
+
|
|
335
|
+
| You learned (profiling) | So in the SDM you must… | Where (SKILL.md workflow) |
|
|
336
|
+
|---|---|---|
|
|
337
|
+
| **Grain** = one row per `<pk>` (`COUNT(*)==COUNT(DISTINCT pk)`) | set that `<pk>` as the metric's **`insightsSettings.identifyingDimension`** (required); never count by summing the ID | Step 8 metric shape |
|
|
338
|
+
| **Grain is entity × snapshot-date** (panel) | build the **latest-flag `Text` calc dimension + metric `filters[]`**, and anchor every snapshot metric on a **constant `As_Of_Date`** calc dimension | `large-flat-file-handling.md §4`; snapshot-anchor box above |
|
|
339
|
+
| A field is a **numeric ID** (dimension, not measure) | use it as a dimension / `identifyingDimension`; to count records point **`measurementReference` at a real numeric measure + `aggregationType: "Count"`** (an ID/dimension is rejected: "field … was not found in the model") | Step 8 |
|
|
340
|
+
| **Additive** measure (amount/count/qty) | `aggregationType: "Sum"` (or `Count`) | Step 8 |
|
|
341
|
+
| **Non-additive** ratio/rate/% (the clean-but-conventional gate) | `Average` / `UserAgg`, or recompute from numerator ÷ denominator — **never `Sum`**; and **never `Auto`** (rejected on metrics) | Step 8; mistake #3 |
|
|
342
|
+
| **Units/scale/sign/currency** convention (from the gate) | bake the correction into the **calc measure expression** (×100, `ABS`, currency split) *before* it backs a metric — the metric inherits whatever the measure emits | Step 8; calc-field step |
|
|
343
|
+
| A **real business event date** exists | use it as the metric's **`timeDimensionReference`** + `timeGrains`; a single constant/plumbing date is not a trend | Step 8 |
|
|
344
|
+
| Only a **constant/plumbing date** (or a deduped snapshot) | anchor on a **constant `DATE("<as-of>")`** calc dimension, not `cdp_sys_*` and not a per-row `LAST_*_TS` | snapshot-anchor box; mistake #4 |
|
|
345
|
+
| Dimension **cardinality** (2–7 / 8–20 / 20+) | drives chart family + whether to top-N/"Other"; a 20+-cardinality field is not a bar/pie | `viz-authoring.md`; Step 10 |
|
|
346
|
+
| A derived **segment / bucket** needs to be **charted** (not just a KPI) | it won't bind as a calc dimension — **materialize it as a native column at DLO→DMO mapping time**, decided *now*, not after the chart fails | "materialize segments" above; Step 10 |
|
|
347
|
+
| A field's **meaning is ambiguous / requested-but-absent** | the elicitation gate already fired — carry the user's confirmed definition (or the flagged assumption) into the calc field; never author on a silent guess | Elicitation gate above |
|
|
348
|
+
| A **1:N relationship** will be added | re-profile the **post-join** grain; aggregate the "one"-side measure on its owning object or via `COUNT(DISTINCT)` to avoid fan-out double-counting | mistake #7; Step 6 relationships (validated Step 7) |
|
|
349
|
+
|
|
350
|
+
This map is the contract between the two halves of this skill: data understanding
|
|
351
|
+
decides *what is true about the data*; the SDM build decides *how to encode it*. Hand
|
|
352
|
+
the right-hand column to the build as settled decisions, not open questions.
|
|
353
|
+
|
|
354
|
+
## Common mistakes
|
|
355
|
+
|
|
356
|
+
1. **Charting before profiling.** Building a viz off the field *labels* without
|
|
357
|
+
checking cardinality, type, or date role. This is how a 200-bar chart or a
|
|
358
|
+
line over a load timestamp ships.
|
|
359
|
+
2. **Treating an ID as a measure.** `Order_ID`, `Account_Id` are dimensions even
|
|
360
|
+
though they're numeric. Counting records goes through a numeric measure +
|
|
361
|
+
`Count` (Step 8), not a sum of the ID.
|
|
362
|
+
3. **Summing a non-additive measure.** Margin %, win rate, average deal size are
|
|
363
|
+
ratios — summing them across groups is meaningless. Use `Average`/`UserAgg`,
|
|
364
|
+
or recompute from the underlying numerator and denominator.
|
|
365
|
+
4. **Picking the wrong time anchor for a metric.** Two forms: (a) a `cdp_sys_*` or
|
|
366
|
+
`*_SourceVersion` **plumbing** date produces a technically-valid but
|
|
367
|
+
business-meaningless trend; (b) on a **deduped snapshot**, a **per-row activity
|
|
368
|
+
timestamp** (`LAST_*_TS`, "last activity") fractures the population across months and
|
|
369
|
+
orphans null-timestamp rows, so the KPI card shows only the latest month's slice
|
|
370
|
+
instead of the true total. A snapshot count/flag-sum metric must anchor on a
|
|
371
|
+
**uniform/constant** as-of date (a `DATE("…")` calc dimension), since the anchor
|
|
372
|
+
is required and the card renders the latest period (see the snapshot-anchor box
|
|
373
|
+
above and the Step 8 rule).
|
|
374
|
+
5. **Ignoring the `null` bucket.** A large `null` grouping row is a data-quality
|
|
375
|
+
signal (unmapped keys, missing values) — surface it; don't let it sit as an
|
|
376
|
+
unlabeled slice on a chart.
|
|
377
|
+
6. **Plotting a trend with one date.** If `MIN(date) == MAX(date)` or there's a
|
|
378
|
+
single distinct date, a timeseries is a single point — choose comparison
|
|
379
|
+
instead.
|
|
380
|
+
7. **Aggregating across a join without re-profiling the grain.** Adding a 1:N
|
|
381
|
+
relationship **fans out** the fact object — a customer joined to its orders now
|
|
382
|
+
has one row per order, so `SUM(customer_value)` double-counts and a `COUNT` of
|
|
383
|
+
customers inflates. The grain you profiled (row 1) was the *pre-join* grain; the
|
|
384
|
+
query runs at the *post-join* grain. After adding a relationship, re-run the
|
|
385
|
+
`COUNT(*)` vs `COUNT(DISTINCT pk)` check on the joined result and confirm a measure
|
|
386
|
+
from the "one" side isn't being summed at the "many" side's multiplied grain (use
|
|
387
|
+
`COUNT(DISTINCT)`, a row-level dedup, or aggregate on the owning object).
|