mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-09 00:42:46 +08:00
Adds a skill for authoring Tableau Next semantic models (SDMs) on Data 360: build from scratch, add data objects, define joins, enrich with calculated fields and metrics, and make models AI-ready. Smoke-tested against a live Data 360 org: SDM discovery, AI-readiness flip, dimension creation, metric creation, and description backfill all exercised end-to-end.
247 lines
10 KiB
Python
247 lines
10 KiB
Python
"""Business-friendly name generation and validation utilities.
|
|
|
|
Provides functions for generating business-friendly visualization names,
|
|
validating names, and cleaning field names for display.
|
|
"""
|
|
|
|
import sys
|
|
from typing import Any, Dict, Optional, Tuple
|
|
|
|
|
|
def validate_business_friendly_name(name: str, label: str) -> Tuple[bool, Optional[str]]:
|
|
"""Validate that name and label are business-friendly.
|
|
|
|
Checks for technical suffixes and ensures names are descriptive.
|
|
|
|
Args:
|
|
name: Visualization API name (e.g., "Sales_Trend_Over_Time")
|
|
label: Visualization display label (e.g., "Sales Performance Over Time")
|
|
|
|
Returns:
|
|
Tuple of (is_valid, error_message)
|
|
- is_valid: True if name is business-friendly, False otherwise
|
|
- error_message: Error message if invalid, None if valid
|
|
|
|
Example:
|
|
>>> is_valid, error = validate_business_friendly_name("Sales_Trend", "Sales Trend")
|
|
>>> print(is_valid)
|
|
True
|
|
"""
|
|
technical_suffixes = ["_Clc", "_clc", "_Mtc", "_mtc", "_MTC", "_CLC"]
|
|
|
|
# Check name
|
|
for suffix in technical_suffixes:
|
|
if suffix in name:
|
|
return False, f"Name '{name}' contains technical suffix '{suffix}'. Use business-friendly names."
|
|
|
|
# Check label
|
|
if len(label) < 5:
|
|
return False, f"Label '{label}' is too short. Use descriptive business labels."
|
|
|
|
# Check label for technical suffixes (case-insensitive)
|
|
label_lower = label.lower()
|
|
for suffix in technical_suffixes:
|
|
if suffix.lower() in label_lower:
|
|
return False, f"Label '{label}' contains technical suffix '{suffix}'. Use business-friendly labels."
|
|
|
|
return True, None
|
|
|
|
|
|
def clean_field_name_for_display(field_name: str, sdm_fields: Dict[str, Dict[str, Any]]) -> str:
|
|
"""Clean field name for display, using SDM label if available.
|
|
|
|
Strips technical suffixes (_Clc, _mtc, etc.) and uses SDM field labels
|
|
when available. Falls back to cleaned field name if label not available.
|
|
|
|
Args:
|
|
field_name: Raw field name (e.g., "Pipeline_Generation_Clc")
|
|
sdm_fields: Dict of SDM field definitions
|
|
|
|
Returns:
|
|
Cleaned business-friendly name (e.g., "Pipeline Generation")
|
|
|
|
Example:
|
|
>>> sdm_fields = {"Pipeline_Generation_Clc": {"label": "Pipeline Generation", ...}}
|
|
>>> cleaned = clean_field_name_for_display("Pipeline_Generation_Clc", sdm_fields)
|
|
>>> print(cleaned)
|
|
"Pipeline Generation"
|
|
"""
|
|
# Try to use SDM label first
|
|
if field_name in sdm_fields:
|
|
label = sdm_fields[field_name].get("label", "")
|
|
if label:
|
|
return label
|
|
|
|
# Clean field name: strip technical suffixes
|
|
cleaned = field_name
|
|
technical_suffixes = ["_Clc", "_clc", "_Mtc", "_mtc", "_MTC", "_CLC"]
|
|
for suffix in technical_suffixes:
|
|
if cleaned.endswith(suffix):
|
|
cleaned = cleaned[:-len(suffix)]
|
|
break
|
|
|
|
# Convert to title case, replacing underscores with spaces
|
|
cleaned = cleaned.replace("_", " ").title()
|
|
|
|
# Remove trailing spaces
|
|
cleaned = cleaned.strip()
|
|
|
|
return cleaned
|
|
|
|
|
|
def generate_business_friendly_name(
|
|
template: str,
|
|
fields: Dict[str, str],
|
|
sdm_fields: Dict[str, Dict[str, Any]]
|
|
) -> Tuple[str, str]:
|
|
"""Generate business-friendly name and label from template and fields.
|
|
|
|
Uses SDM field labels and strips technical suffixes to ensure business-friendly names.
|
|
Maps template types to business intent descriptions.
|
|
|
|
Args:
|
|
template: Template name (e.g., "multi_series_line", "revenue_by_category")
|
|
fields: Dict mapping template field names to SDM field names
|
|
sdm_fields: Dict of SDM field definitions (for labels)
|
|
|
|
Returns:
|
|
Tuple of (api_name, display_label)
|
|
|
|
Example:
|
|
>>> fields = {"category": "Account_Industry", "amount": "Total_Amount"}
|
|
>>> sdm_fields = discover_sdm_fields("Sales_Model")
|
|
>>> name, label = generate_business_friendly_name("revenue_by_category", fields, sdm_fields)
|
|
>>> print(label)
|
|
"Account Industry Analysis"
|
|
"""
|
|
# Map template to business intent/description
|
|
intent_map = {
|
|
"multi_series_line": "Trend Over Time",
|
|
"trend_over_time": "Trend Over Time",
|
|
"revenue_by_category": "Analysis by",
|
|
"stacked_bar_by_dimension": "Breakdown by",
|
|
"bar_multi_measure": "Comparison by",
|
|
"heatmap_grid": "Analysis",
|
|
"scatter_correlation": "Correlation Analysis",
|
|
"market_share_donut": "Distribution",
|
|
"conversion_funnel": "Pipeline by",
|
|
"dot_matrix": "Multi-Dimensional Analysis",
|
|
"top_n_leaderboard": "Rankings",
|
|
"geomap_location_only": "Locations Map",
|
|
"geomap_points": "Map by",
|
|
"geomap_advanced": "Map by",
|
|
"flow_sankey": "Flow from",
|
|
"flow_simple": "Flow from",
|
|
"flow_simple_measure_on_marks": "Flow from",
|
|
"flow_sankey_measure_on_marks": "Flow from",
|
|
"flow_package_base": "Flow from",
|
|
"flow_package_single_color": "Flow from",
|
|
"flow_package_link_color_nodes_color": "Flow from",
|
|
"flow_package_colors_variations": "Flow from",
|
|
"flow_package_three_level": "Flow from",
|
|
}
|
|
|
|
# Get primary field for context (using cleaned field names)
|
|
if "category" in fields:
|
|
main_field = clean_field_name_for_display(fields["category"], sdm_fields)
|
|
intent = intent_map.get(template, "Analysis")
|
|
if intent == "Analysis by":
|
|
label = f"{main_field} Analysis"
|
|
elif intent == "Breakdown by":
|
|
label = f"{main_field} Breakdown"
|
|
elif intent == "Comparison by":
|
|
label = f"{main_field} Comparison"
|
|
elif intent == "Distribution":
|
|
label = f"{main_field} Distribution"
|
|
elif intent == "Pipeline by":
|
|
label = f"Sales Pipeline by {main_field}"
|
|
else:
|
|
label = f"{main_field} {intent}"
|
|
name = label.replace(" ", "_")
|
|
|
|
elif "date" in fields:
|
|
measure_name = fields.get("measure", "Value")
|
|
measure = clean_field_name_for_display(measure_name, sdm_fields) if measure_name in sdm_fields else measure_name.replace("_", " ").title()
|
|
intent = intent_map.get(template, "Trend")
|
|
if "multi_series" in template or "color_dim" in fields:
|
|
color_dim_name = fields.get("color_dim", "")
|
|
color_dim = clean_field_name_for_display(color_dim_name, sdm_fields) if color_dim_name and color_dim_name in sdm_fields else color_dim_name.replace("_", " ").title()
|
|
if color_dim:
|
|
label = f"{measure} Trend by {color_dim}"
|
|
else:
|
|
label = f"{measure} {intent}"
|
|
else:
|
|
label = f"{measure} {intent}"
|
|
name = label.replace(" ", "_")
|
|
|
|
elif "row_dim" in fields and "col_dim" in fields:
|
|
row_name = fields["row_dim"]
|
|
col_name = fields["col_dim"]
|
|
row = clean_field_name_for_display(row_name, sdm_fields)
|
|
col = clean_field_name_for_display(col_name, sdm_fields)
|
|
measure_name = fields.get("measure", "Performance")
|
|
measure = clean_field_name_for_display(measure_name, sdm_fields) if measure_name in sdm_fields else measure_name.replace("_", " ").title()
|
|
if template == "heatmap_grid":
|
|
label = f"{measure} by {row} and {col}"
|
|
elif template == "dot_matrix":
|
|
label = f"{measure} Analysis: {row} vs {col}"
|
|
else:
|
|
label = f"{row} by {col}"
|
|
name = label.replace(" ", "_")
|
|
|
|
elif "x_measure" in fields and "y_measure" in fields:
|
|
x_measure_name = fields["x_measure"]
|
|
y_measure_name = fields["y_measure"]
|
|
x_measure = clean_field_name_for_display(x_measure_name, sdm_fields)
|
|
y_measure = clean_field_name_for_display(y_measure_name, sdm_fields)
|
|
category_name = fields.get("category", "")
|
|
category = clean_field_name_for_display(category_name, sdm_fields) if category_name and category_name in sdm_fields else category_name.replace("_", " ").title()
|
|
if category:
|
|
label = f"{x_measure} vs {y_measure} by {category}"
|
|
else:
|
|
label = f"{x_measure} vs {y_measure}"
|
|
name = label.replace(" ", "_")
|
|
|
|
elif "stage" in fields:
|
|
stage_name = fields["stage"]
|
|
stage = clean_field_name_for_display(stage_name, sdm_fields)
|
|
count_name = fields.get("count", "Value")
|
|
measure = clean_field_name_for_display(count_name, sdm_fields) if count_name in sdm_fields else count_name.replace("_", " ").title()
|
|
label = f"{measure} by {stage}"
|
|
name = label.replace(" ", "_")
|
|
|
|
elif template == "geomap_location_only" and "latitude" in fields and "longitude" in fields:
|
|
label = "Locations (Map)"
|
|
name = label.replace(" ", "_")
|
|
|
|
elif "latitude" in fields and "longitude" in fields and "label_dim" in fields:
|
|
lab_name = fields["label_dim"]
|
|
lab = clean_field_name_for_display(lab_name, sdm_fields) if lab_name in sdm_fields else lab_name.replace("_", " ").title()
|
|
measure_name = fields.get("measure", "Value")
|
|
measure = clean_field_name_for_display(measure_name, sdm_fields) if measure_name in sdm_fields else measure_name.replace("_", " ").title()
|
|
if template == "geomap_advanced":
|
|
label = f"{measure} on map by {lab} (color & size)"
|
|
else:
|
|
label = f"{measure} by {lab} (Map)"
|
|
name = label.replace(" ", "_")
|
|
|
|
elif "level1" in fields and "level2" in fields:
|
|
l1n, l2n = fields["level1"], fields["level2"]
|
|
l1 = clean_field_name_for_display(l1n, sdm_fields) if l1n in sdm_fields else l1n.replace("_", " ").title()
|
|
l2 = clean_field_name_for_display(l2n, sdm_fields) if l2n in sdm_fields else l2n.replace("_", " ").title()
|
|
label = f"Flow from {l1} to {l2}"
|
|
name = label.replace(" ", "_")
|
|
|
|
else:
|
|
# Fallback: use template name with field context
|
|
intent = intent_map.get(template, template.replace("_", " ").title())
|
|
if fields:
|
|
first_field_name = list(fields.values())[0]
|
|
first_field = clean_field_name_for_display(first_field_name, sdm_fields) if first_field_name in sdm_fields else first_field_name.replace("_", " ").title()
|
|
label = f"{first_field} {intent}"
|
|
else:
|
|
label = intent
|
|
name = label.replace(" ", "_")
|
|
|
|
return name, label
|