Imported from clssck/agent-plugins (
plugins/coco-bundled-skills/skills/agent-studio/semantic-view/import_powerbi/SKILL.md). Install upstream withnpx skills add clssck/agent-plugins --skill import_powerbi. Copyright stays with the author.
Power BI Import Skill
Import Power BI templates (.pbit) and desktop files (.pbix) into Snowflake Semantic Views.
Tool Restrictions
Use the cortex agent-studio CLI for ALL operations.
Forbidden: Do NOT use read, write, edit, multi_edit, or bash tools on semantic view YAML files. Do NOT use Python scripts. These bypass cortex_project/ tracking.
Invocation Pattern
Both pbi_analyze and pbi_export are called via cortex agent-studio backend. The --parameters flag takes a JSON string:
cortex agent-studio backend --tool pbi_analyze \
--parameters '{"file_path": "@DATABASE.SCHEMA.STAGE/file.pbit"}'
Passing malformed JSON will cause a parse error.
Workflow
Step 1: Verify File Access
LIST @DATABASE.SCHEMA.STAGE PATTERN='.*\\.pbi[tx]';
If not found: check path format (@DATABASE.SCHEMA.STAGE/filename), verify access (SHOW STAGES IN SCHEMA).
.pbit (template, JSON DataModelSchema) and .pbix (desktop, XPress9-compressed VertiPaq) are both supported. The parser auto-detects by ZIP contents.
Step 2: Analyze the File
Read the reference first, then call analyze:
Read: semantic-view/reference/pbi_tool_reference.md
⚠️ The parameter is file_path — NOT stage_path, NOT path. This is the #1 analyze failure.
cortex agent-studio backend --tool pbi_analyze \
--parameters '{"file_path": "@DATABASE.SCHEMA.STAGE/file.pbit", "large_threshold": 100}'
pbi_analyze parameters:
| Parameter | Type | Required | Notes |
|---|---|---|---|
file_path |
string | Yes | Stage path @DATABASE.SCHEMA.STAGE/file.pbit. NOT stage_path or path |
large_threshold |
integer | No | Object count threshold (default 100). Must be positive |
validate_in_snowflake |
boolean | No | Default false. Opt-in validate_tables(...) Snowflake check |
The response is a JSON wrapper — parse the result string. Key fields: file_type, total_tables, total_physical_columns, total_calculated_columns, total_measures, total_relationships, is_large_file, tables, relationships, measures, m_query_warnings, validation (only when validate_in_snowflake=true), warnings.
Present findings to the user:
- Summary table: file type (
pbit/pbix), total tables, physical + calculated columns, total measures, total relationships,is_large_file. - Table list: show each
tables[*]entry'sname,database,db_schema,snowflake_table_name, count ofphysical_columns/calculated_columns, andmeasure_count. - Measure list (if non-empty): show measure
namevalues; truncatedax_expressionpreviews to keep output manageable. - Warning summary — group by category:
- M-query unresolved tables (from
m_query_warnings) — tables that did not resolve to a Snowflake DB+schema (parameterized-null source, non-Snowflake source); excluded from export. - Validation warnings (only if
validate_in_snowflake=true) — tables/columns that won't passvalidate_tables.
- M-query unresolved tables (from
- Filtering options (if
is_large_fileor user wants to narrow scope):- By tables: show table
namevalues fromtables[*].name. - By columns: show
physical_columnsandcalculated_columnsfrom each table — both are filterable byinclude_columns. - By measures: show measure
namevalues forinclude_measures.
- By tables: show table
- Snowflake validation — if the user wants to confirm tables exist before exporting, offer to re-run analyze with
validate_in_snowflake: true(it's opt-in and not free).
Wait for user approval before proceeding.
Step 3: Export the Power BI File
⚠️ MANDATORY — Re-read the reference before building the export call:
Read: semantic-view/reference/pbi_tool_reference.md
This re-read is critical — by this point the analyze results and user conversation have pushed the parameter details out of context. Without re-reading, you WILL use wrong parameter names.
⚠️ PARAMETER CHECKLIST — Verify EVERY parameter name before calling:
| ✅ Correct Name | ❌ WRONG — Never Use These |
|---|---|
file_path |
stage_pathpathfilepath |
semantic_model_name |
namemodel_namemodel |
include_tables |
tablestable_namesinclude_table |
include_columns |
columnscolumn_namesinclude_column |
include_measures |
measuresmeasure_namesinclude_measure |
include_measures_all |
include_all_measuresall_measuresinclude_all |
include_calculations |
include_calculationcalculationsinclude_calculated_columns |
skip_table_validation |
no_validationskip_validationdisable_validation |
⚠️ include_measures vs include_measures_all — these are DIFFERENT parameters:
include_measures: list[string] — keep only the named measures (filters which ones survive)include_measures_all: boolean — whenfalse, drop every measure regardless ofinclude_measures
Full pbi_export parameters:
| Parameter | Type | Required | Notes |
|---|---|---|---|
file_path |
string | Yes | Same stage path used in analyze. NOT stage_path |
semantic_model_name |
string | Yes | Alphanumeric, underscores, hyphens only. NOT name or model_name |
target_database |
string | No | Default "". Override DB on every table's base_table |
target_schema |
string | No | Default "". Override schema on every table's base_table |
include_tables |
list[string] | No | Filter to specific table display names. Case-sensitive exact match |
include_columns |
list[string] | No | Filter to specific column names (physical AND calculated) |
include_measures |
list[string] | No | Filter to specific DAX measure names |
include_calculations |
boolean | No | Default true. Set false to drop all calculated columns |
include_measures_all |
boolean | No | Default true. Set false to drop ALL measures |
skip_table_validation |
boolean | No | Default false. Skip Snowflake validate_tables (use with target_database/target_schema remap) |
generate_descriptions |
boolean | No | Default false. LLM enrichment of metric descriptions |
model_name |
string | No | Default "ANTHROPIC_CLAUDE_SONNET_4". LLM model used when generate_descriptions=true |
Filter order of operations: include_tables → include_columns / include_measures → include_calculations / include_measures_all. Filtering happens on the parsed model before validate_tables runs.
Call cortex agent-studio backend --tool pbi_export. All parameters except file_path and semantic_model_name are optional — include only what you need:
cortex agent-studio backend --tool pbi_export \
--parameters '{"file_path": "@DATABASE.SCHEMA.STAGE/file.pbit", "semantic_model_name": "sales_model", "target_database": "ANALYTICS", "target_schema": "PUBLIC"}'
Minimal call (required params only):
cortex agent-studio backend --tool pbi_export \
--parameters '{"file_path": "@DATABASE.SCHEMA.STAGE/file.pbit", "semantic_model_name": "my_model"}'
The export result contains yaml_content (the semantic view YAML), plus table_count, column_count, relationship_count, metric_count, unsupported_measure_count, descriptions_generated, m_query_warnings, validation_warnings, errors, and warnings.
Step 4: Save the YAML to Workspace
After the export succeeds, save the YAML using cortex agent-studio sv-write. Pass the yaml_content from the export result directly:
cortex agent-studio sv-write \
--yaml-content '<the yaml_content string from the export result>' \
--source-object ANALYTICS.PUBLIC.SALES_MODEL
--source-object should be DATABASE.SCHEMA.MODEL_NAME matching the target location. Do NOT pass --file-path — it is auto-generated.
⚠️ Never pass --yaml-content inline in bash — shell argument length limits silently truncate large strings, producing a corrupt file. Instead, save the export result to a file, extract yaml_content to a temp file using Python, then pass via $(cat):
# Save export output, extract yaml_content, then sv-write
cortex agent-studio backend --tool <tool> --parameters '...' > /tmp/export_result.json
# Use Python to extract yaml_content:
# result["data"]["result"] is a JSON string — parse it, then get ["yaml_content"]
# Write to /tmp/model.sv.yaml, then:
cortex agent-studio sv-write \
--yaml-content "$(cat /tmp/model.sv.yaml)" \
--source-object DATABASE.SCHEMA.MODEL_NAME
After the write completes:
-
Don't display the full YAML — show summary stats (
table_count,column_count,relationship_count,metric_count). -
Unsupported measures (if
unsupported_measure_count > 0): explain that some DAX measures couldn't be transpiled; they were dropped. Recreate as SQL metrics if needed. (Power BI v1 does NOT auto-create custom views for un-transpilable DAX — unlike the Tableau import.) -
Warnings — categorize and present:
- M-query unresolved tables (
m_query_warnings) — tables dropped at parse time; not in YAML. - Validation warnings (
validation_warnings) — tables/columns rejected byvalidate_tables; not in YAML. Empty whenskip_table_validation=true. - Builder warnings — anything else flagged during proto build.
- Filter banner — informational summary of which filters were applied (always present in
warningswhen filters were used).
- M-query unresolved tables (
Step 5: Verify Table References
Parse the saved YAML for base_table references, then verify each exists:
SHOW TABLES LIKE '{table_name}' IN SCHEMA {database}.{schema};
If tables are missing (expected when importing from a different environment): offer to re-export with target_database / target_schema (and consider skip_table_validation: true to bypass the pre-build validation while remapping). Help discover where data lives:
SHOW DATABASES;
SHOW SCHEMAS IN DATABASE <database>;
SHOW TABLES IN SCHEMA <database>.<schema>;
Step 6: Deploy (optional)
If the user wants to deploy, load upload/SKILL.md and follow its process. Only deploy when explicitly requested.
Missing Base Tables
If pbi_analyze reports m_query_warnings for unresolved tables, or if export returns validation_warnings saying tables don't exist, the M expressions in the Power BI file resolve to a Snowflake location (database/schema) that isn't present in the current account.
Resolution:
- Find a database/schema in this account that contains compatible physical tables (matching column names).
- Re-run the export (Step 3) with
target_databaseandtarget_schemaso everybase_tablein the YAML points there. Addskip_table_validation: trueif the validation step still rejects the original references:
cortex agent-studio backend --tool pbi_export \
--parameters '{"file_path": "@DATABASE.SCHEMA.STAGE/file.pbit", "semantic_model_name": "my_model", "target_database": "ANALYTICS", "target_schema": "PUBLIC", "skip_table_validation": true}'
- Save the resulting YAML (Step 4), then re-verify table references (Step 5) against the new location.
Power BI Specifics
A few things differ from the Tableau flow:
- No published-datasource concept. No
additional_filesorpublished_datasource_stub_name—.pbit/.pbixis self-contained. - Both
.pbitand.pbixare accepted..pbitcarries the model JSON directly;.pbixrequires extracting the compressed VertiPaq blob (slower; allow longer timeouts). - Snowflake validation on analyze is opt-in. Pass
validate_in_snowflake: trueto surface table-existence issues up front. Off by default to keep analyze cheap. - Empty filter result is a hard error. If
include_tablesfilters out every table, the tool returnsBAD_REQUEST: "All tables filtered out — check include_tables names"rather than an empty success. - DAX measures that can't be transpiled are silently dropped (and counted in
unsupported_measure_count); no view-creation analog exists in v1. - Filtering happens before validation.
validate_tablesonly sees what survives the filter, so missing-table errors disappear when you narrow scope.
Stopping Points
- After Step 2: Present analysis, wait for user approval
- After Step 4: If unsupported measures are reported, ask before recreating any as SQL metrics
- After Step 5: If table references need remapping, ask before re-exporting
Error Handling
- File not found — Check stage path format and permissions (
LIST @STAGE) - Unsupported file type — Only
.pbitand.pbixare accepted large_thresholdmust be positive — Use any integer ≥ 1- Local/relative path rejected —
Only Snowflake stage paths are supported. Always use@DATABASE.SCHEMA.STAGE/... semantic_model_name is invalid— Must match^[A-Za-z0-9_-]+$(no spaces, no dots, no slashes)- All tables filtered out —
include_tablesnames didn't match. Names are case-sensitive — re-check against analyze output - Validation warnings dropping tables — Tables don't exist at the M-resolved location. See "Missing Base Tables" above
- Parameter not taking effect —
include_measures_all(boolean) is NOT the same asinclude_measures(list). Re-read the reference and the Parameter Checklist in Step 3 - Wrong parameter name error — Re-read the reference. Common mistakes:
stage_path(usefile_path),tables(useinclude_tables),no_validation(useskip_table_validation) - Export failed — Re-read the reference, fix the parameters, retry the export call