Data Source Connector
Universal data-source adapter for the Power BI dashboard pipeline. This skill abstracts away the specifics of every supported source and produces a single, normalized artifact — data-model.json — that downstream skills (semantic-mapper, visual-selector, visual-generator) can consume without knowing where the data actually came from.
When to Use This Skill
- The orchestrator (or the user) has identified a source but no structured data model exists
- The agent needs to enumerate tables, columns, types, and relationships from any backend
- A Genie YAML metric view is not available (use this skill instead of
semantic-mapper's YAML path)
- The agent needs the source-specific M-Code / connection block to embed in TMDL partitions
Supported Sources
| Source Type |
type value |
Required Inputs |
| Databricks |
databricks |
hostname, warehouse_id, catalog, schema |
| Snowflake |
snowflake |
account, warehouse, database, schema, role |
| BigQuery |
bigquery |
project, dataset |
| Azure Synapse |
synapse |
server, database |
| SQL Server |
sqlserver |
server, database |
| PostgreSQL |
postgres |
host, port, database, schema |
| MySQL |
mysql |
host, port, database |
| Oracle |
oracle |
host, port, service_name |
| Excel |
excel |
path (.xlsx file); each sheet becomes a table |
| CSV |
csv |
path or directory of CSVs; each file becomes a table |
| Parquet |
parquet |
path or directory |
| OData |
odata |
service_url |
| REST API |
rest |
base_url, endpoints[], auth |
| SharePoint Lists |
sharepoint |
site_url, list_names[] |
Inputs
- Source descriptor —
{ type, ...connection params } from the orchestrator or the user
- Scope hint (optional) — which tables / sheets / endpoints to include (defaults to all)
- Sampling preference (optional) — number of sample rows to capture per table (default 5)
Outputs
Primary: data-model.json
Source-agnostic, normalized representation. Schema:
{
"source": {
"type": "excel",
"connection": { "path": "C:/data/sales.xlsx" },
"discoveredAt": "2026-05-13T11:00:00Z"
},
"tables": [
{
"name": "fact_sales",
"physicalName": "Orders",
"role": "fact",
"grain": "one row per order line",
"rowCountEstimate": 50000,
"columns": [
{
"name": "order_id",
"physicalName": "OrderID",
"dataType": "int64",
"sourceProviderType": "bigint",
"isPrimaryKey": true,
"nullable": false
},
{
"name": "customer_key",
"physicalName": "CustomerID",
"dataType": "int64",
"sourceProviderType": "bigint",
"isForeignKey": true,
"foreignKey": { "table": "dim_customer", "column": "customer_key" }
},
{
"name": "total_value",
"physicalName": "TotalValue",
"dataType": "double",
"sourceProviderType": "double",
"formatHint": "currency"
}
],
"sampleRows": [
{ "order_id": 1, "customer_key": 42, "total_value": 199.99 }
]
}
],
"relationships": [
{
"from": { "table": "fact_sales", "column": "customer_key" },
"to": { "table": "dim_customer", "column": "customer_key" },
"cardinality": "many-to-one",
"isActive": true,
"inferredFrom": "naming convention"
}
],
"mCodeAdapter": {
"mode": "import",
"templates": {
"fact_sales": "let Source = Excel.Workbook(File.Contents(\"C:/data/sales.xlsx\"), null, true), Orders_Sheet = Source{[Item=\"Orders\",Kind=\"Sheet\"]}[Data], #\"Promoted Headers\" = Table.PromoteHeaders(Orders_Sheet, [PromoteAllScalars=true]) in #\"Promoted Headers\""
}
},
"openQuestions": [
{
"id": "q1",
"scope": "relationship",
"question": "Is the relationship between fact_sales.customer_key and dim_customer.customer_key correct? It was inferred from column-name similarity, not from a foreign key constraint."
}
]
}
Secondary: Clarification report
If schema cannot be fully discovered (missing credentials, ambiguous grain, no FK constraints), emit openQuestions[] for the orchestrator to surface to the user.
Workflow
Step 1: Validate Connection Inputs
For each source type, check that required inputs are present. If any are missing, emit a clarification question and stop.
Examples:
databricks missing warehouse_id → ask: "What is the Databricks SQL warehouse ID?"
excel missing path → ask: "What is the full path to the Excel file?"
sqlserver missing credentials → ask: "Is this a trusted-connection database, or do I need a username and password?"
See references/clarification-questions.md for the full question bank.
Step 2: Probe the Source
Run scripts/introspect_source.py (or the source-specific adapter) to:
- List tables / sheets / endpoints
- For each table: list columns with native types, nullability, primary key flags
- Sample rows: pull 5 sample rows per table (configurable)
- Foreign key discovery:
- If the source supports FK constraints (SQL databases) → use them directly
- Otherwise → infer from column-name patterns (
<table>_key, <table>_id, identical names across tables) and flag as inferredFrom: naming convention for user confirmation at Gate A
Step 3: Classify Tables (Fact vs. Dimension)
Heuristics:
| Signal |
Likely Role |
| Has multiple FK columns + a numeric measure column |
fact |
Name contains fact_, sales, orders, transactions, events |
fact |
Name contains dim_, customers, products, dates, geography |
dimension |
| Only one PK column + descriptive columns |
dimension |
| Has a date column with daily continuity |
date dimension |
If unsure, add to openQuestions[] and ask the user at Gate A.
Step 4: Normalize Types
Map source-native types to TMDL types (consumed by semantic-mapper):
| Source Type |
dataType |
sourceProviderType |
STRING, VARCHAR, NVARCHAR, TEXT |
string |
nvarchar(65535) |
INT, INTEGER, INT32 |
int64 |
int |
BIGINT, LONG |
int64 |
bigint |
DOUBLE, FLOAT, REAL, DECIMAL, NUMERIC |
double |
double |
DATE |
dateTime |
date |
DATETIME, TIMESTAMP, DATETIME2 |
dateTime |
datetime2 |
BOOLEAN, BIT |
boolean |
bit |
For Excel/CSV with no declared types, sniff from sample rows.
Step 5: Generate the M-Code Adapter Block
Each source has a different M-Code template. Per-source templates live in references/connection-patterns.md. Examples:
Databricks (DirectQuery):
let
Source = DatabricksMultiCloud.Catalogs("<hostname>", "/sql/1.0/warehouses/<warehouse_id>", [Catalog = "", Database = ""]),
<catalog>_Database = Source{[Name="<catalog>",Kind="Database"]}[Data],
<schema>_Schema = <catalog>_Database{[Name="<schema>",Kind="Schema"]}[Data],
<table>_Table = <schema>_Schema{[Name="<table>",Kind="Table"]}[Data]
in
<table>_Table
Excel (Import):
let
Source = Excel.Workbook(File.Contents("<path>"), null, true),
<sheet>_Sheet = Source{[Item="<sheet>",Kind="Sheet"]}[Data],
#"Promoted Headers" = Table.PromoteHeaders(<sheet>_Sheet, [PromoteAllScalars=true])
in
#"Promoted Headers"
CSV (Import):
let
Source = Csv.Document(File.Contents("<path>"), [Delimiter=",", Columns=<n>, Encoding=65001, QuoteStyle=QuoteStyle.Csv]),
#"Promoted Headers" = Table.PromoteHeaders(Source, [PromoteAllScalars=true])
in
#"Promoted Headers"
SQL Server (DirectQuery):
let
Source = Sql.Database("<server>", "<database>"),
<schema>_<table> = Source{[Schema="<schema>",Item="<table>"]}[Data]
in
<schema>_<table>
The connector emits one M-Code block per table into data-model.json → mCodeAdapter.templates.
Step 6: Mode Selection
Set mCodeAdapter.mode based on source:
| Source |
Mode |
| Databricks, Snowflake, BigQuery, Synapse, SQL Server (large), Oracle |
directQuery |
| Excel, CSV, Parquet, SharePoint List, REST API |
import |
| Postgres, MySQL |
directQuery if user opts in, otherwise import |
Step 7: Emit openQuestions
Append a question for every uncertainty:
- Ambiguous fact/dimension classification
- Inferred (not declared) foreign keys
- Multiple date columns (which is the primary date?)
- Tables with no measurable columns (skip or include?)
- Files with multiple sheets where some look like junk (e.g., metadata, instructions)
Outputs Handed to Downstream
| File |
Consumer |
Purpose |
data-model.json |
semantic-mapper, visual-selector, nlq-dashboard-orchestrator |
Normalized model |
data-model.json.openQuestions[] |
nlq-dashboard-orchestrator (Gate A) |
Drive clarification dialog |
data-model.json.mCodeAdapter |
semantic-mapper |
TMDL partition source blocks |
Validation Checklist
- Every table has at least one column
- Every relationship references existing
{table, column} on both sides
- Every column has both
dataType and sourceProviderType
- Exactly zero or one date-dimension table is flagged as
role: "date dimension" per date role
- At least one table is classified as
fact
mCodeAdapter.templates has one entry per table in tables[]
openQuestions is empty OR every entry has a unique id and a non-empty question
Error Handling
| Error |
Resolution |
| Cannot reach source |
Surface error verbatim; ask user for corrected connection params |
| Authentication failed |
Ask user for credentials; never store them in data-model.json |
| Empty schema (no tables found) |
Stop and ask the user to verify scope |
| Table with zero columns |
Skip and log a warning |
| Source type unsupported |
Ask user to convert to a supported source (e.g., export DB query to CSV) |
Resources
scripts/introspect_source.py — Main connector entry point; routes to source-specific adapters
references/connection-patterns.md — Per-source connection recipes and M-Code templates
references/clarification-questions.md — Standard question bank for missing/ambiguous inputs
references/data-model-schema.md — Full JSON schema for data-model.json
1---2name: data-source-connector3description: Source-agnostic adapter that introspects any data storage (SQL databases, cloud warehouses, Excel/CSV files, OData/REST APIs, SharePoint lists) and emits a normalized data-model.json describing tables, columns, types, and relationships. Use this skill at the start of the dashboard pipeline whenever the user has not provided a structured data model. It also emits a clarification question list when schema cannot be auto-discovered, and produces the source-specific M-Code / import block consumed by semantic-mapper.4---56# Data Source Connector78Universal data-source adapter for the Power BI dashboard pipeline. This skill abstracts away the specifics of every supported source and produces a single, normalized artifact — `data-model.json` — that downstream skills (`semantic-mapper`, `visual-selector`, `visual-generator`) can consume without knowing where the data actually came from.910## When to Use This Skill1112- The orchestrator (or the user) has identified a source but no structured data model exists13- The agent needs to enumerate tables, columns, types, and relationships from any backend14- A Genie YAML metric view is **not** available (use this skill instead of `semantic-mapper`'s YAML path)15- The agent needs the source-specific M-Code / connection block to embed in TMDL partitions1617## Supported Sources1819| Source Type | `type` value | Required Inputs |20|---|---|---|21| Databricks | `databricks` | `hostname`, `warehouse_id`, `catalog`, `schema` |22| Snowflake | `snowflake` | `account`, `warehouse`, `database`, `schema`, `role` |23| BigQuery | `bigquery` | `project`, `dataset` |24| Azure Synapse | `synapse` | `server`, `database` |25| SQL Server | `sqlserver` | `server`, `database` |26| PostgreSQL | `postgres` | `host`, `port`, `database`, `schema` |27| MySQL | `mysql` | `host`, `port`, `database` |28| Oracle | `oracle` | `host`, `port`, `service_name` |29| Excel | `excel` | `path` (`.xlsx` file); each sheet becomes a table |30| CSV | `csv` | `path` or directory of CSVs; each file becomes a table |31| Parquet | `parquet` | `path` or directory |32| OData | `odata` | `service_url` |33| REST API | `rest` | `base_url`, `endpoints[]`, `auth` |34| SharePoint Lists | `sharepoint` | `site_url`, `list_names[]` |3536## Inputs3738- **Source descriptor** — `{ type, ...connection params }` from the orchestrator or the user39- **Scope hint** (optional) — which tables / sheets / endpoints to include (defaults to all)40- **Sampling preference** (optional) — number of sample rows to capture per table (default 5)4142## Outputs4344### Primary: `data-model.json`4546Source-agnostic, normalized representation. Schema:4748```json49{50 "source": {51 "type": "excel",52 "connection": { "path": "C:/data/sales.xlsx" },53 "discoveredAt": "2026-05-13T11:00:00Z"54 },55 "tables": [56 {57 "name": "fact_sales",58 "physicalName": "Orders",59 "role": "fact",60 "grain": "one row per order line",61 "rowCountEstimate": 50000,62 "columns": [63 {64 "name": "order_id",65 "physicalName": "OrderID",66 "dataType": "int64",67 "sourceProviderType": "bigint",68 "isPrimaryKey": true,69 "nullable": false70 },71 {72 "name": "customer_key",73 "physicalName": "CustomerID",74 "dataType": "int64",75 "sourceProviderType": "bigint",76 "isForeignKey": true,77 "foreignKey": { "table": "dim_customer", "column": "customer_key" }78 },79 {80 "name": "total_value",81 "physicalName": "TotalValue",82 "dataType": "double",83 "sourceProviderType": "double",84 "formatHint": "currency"85 }86 ],87 "sampleRows": [88 { "order_id": 1, "customer_key": 42, "total_value": 199.99 }89 ]90 }91 ],92 "relationships": [93 {94 "from": { "table": "fact_sales", "column": "customer_key" },95 "to": { "table": "dim_customer", "column": "customer_key" },96 "cardinality": "many-to-one",97 "isActive": true,98 "inferredFrom": "naming convention"99 }100 ],101 "mCodeAdapter": {102 "mode": "import",103 "templates": {104 "fact_sales": "let Source = Excel.Workbook(File.Contents(\"C:/data/sales.xlsx\"), null, true), Orders_Sheet = Source{[Item=\"Orders\",Kind=\"Sheet\"]}[Data], #\"Promoted Headers\" = Table.PromoteHeaders(Orders_Sheet, [PromoteAllScalars=true]) in #\"Promoted Headers\""105 }106 },107 "openQuestions": [108 {109 "id": "q1",110 "scope": "relationship",111 "question": "Is the relationship between fact_sales.customer_key and dim_customer.customer_key correct? It was inferred from column-name similarity, not from a foreign key constraint."112 }113 ]114}115```116117### Secondary: Clarification report118119If schema cannot be fully discovered (missing credentials, ambiguous grain, no FK constraints), emit `openQuestions[]` for the orchestrator to surface to the user.120121## Workflow122123### Step 1: Validate Connection Inputs124125For each source type, check that required inputs are present. If any are missing, emit a clarification question and stop.126127Examples:128129- `databricks` missing `warehouse_id` → ask: *"What is the Databricks SQL warehouse ID?"*130- `excel` missing `path` → ask: *"What is the full path to the Excel file?"*131- `sqlserver` missing credentials → ask: *"Is this a trusted-connection database, or do I need a username and password?"*132133See `references/clarification-questions.md` for the full question bank.134135### Step 2: Probe the Source136137Run `scripts/introspect_source.py` (or the source-specific adapter) to:1381391. **List tables / sheets / endpoints**1402. **For each table**: list columns with native types, nullability, primary key flags1413. **Sample rows**: pull 5 sample rows per table (configurable)1424. **Foreign key discovery**:143 - If the source supports FK constraints (SQL databases) → use them directly144 - Otherwise → infer from column-name patterns (`<table>_key`, `<table>_id`, identical names across tables) and flag as `inferredFrom: naming convention` for user confirmation at Gate A145146### Step 3: Classify Tables (Fact vs. Dimension)147148Heuristics:149150| Signal | Likely Role |151|---|---|152| Has multiple FK columns + a numeric measure column | `fact` |153| Name contains `fact_`, `sales`, `orders`, `transactions`, `events` | `fact` |154| Name contains `dim_`, `customers`, `products`, `dates`, `geography` | `dimension` |155| Only one PK column + descriptive columns | `dimension` |156| Has a date column with daily continuity | `date dimension` |157158If unsure, add to `openQuestions[]` and ask the user at Gate A.159160### Step 4: Normalize Types161162Map source-native types to TMDL types (consumed by `semantic-mapper`):163164| Source Type | `dataType` | `sourceProviderType` |165|---|---|---|166| `STRING`, `VARCHAR`, `NVARCHAR`, `TEXT` | `string` | `nvarchar(65535)` |167| `INT`, `INTEGER`, `INT32` | `int64` | `int` |168| `BIGINT`, `LONG` | `int64` | `bigint` |169| `DOUBLE`, `FLOAT`, `REAL`, `DECIMAL`, `NUMERIC` | `double` | `double` |170| `DATE` | `dateTime` | `date` |171| `DATETIME`, `TIMESTAMP`, `DATETIME2` | `dateTime` | `datetime2` |172| `BOOLEAN`, `BIT` | `boolean` | `bit` |173174For Excel/CSV with no declared types, sniff from sample rows.175176### Step 5: Generate the M-Code Adapter Block177178Each source has a different M-Code template. Per-source templates live in `references/connection-patterns.md`. Examples:179180**Databricks (DirectQuery):**181182```text183let184 Source = DatabricksMultiCloud.Catalogs("<hostname>", "/sql/1.0/warehouses/<warehouse_id>", [Catalog = "", Database = ""]),185 <catalog>_Database = Source{[Name="<catalog>",Kind="Database"]}[Data],186 <schema>_Schema = <catalog>_Database{[Name="<schema>",Kind="Schema"]}[Data],187 <table>_Table = <schema>_Schema{[Name="<table>",Kind="Table"]}[Data]188in189 <table>_Table190```191192**Excel (Import):**193194```text195let196 Source = Excel.Workbook(File.Contents("<path>"), null, true),197 <sheet>_Sheet = Source{[Item="<sheet>",Kind="Sheet"]}[Data],198 #"Promoted Headers" = Table.PromoteHeaders(<sheet>_Sheet, [PromoteAllScalars=true])199in200 #"Promoted Headers"201```202203**CSV (Import):**204205```text206let207 Source = Csv.Document(File.Contents("<path>"), [Delimiter=",", Columns=<n>, Encoding=65001, QuoteStyle=QuoteStyle.Csv]),208 #"Promoted Headers" = Table.PromoteHeaders(Source, [PromoteAllScalars=true])209in210 #"Promoted Headers"211```212213**SQL Server (DirectQuery):**214215```text216let217 Source = Sql.Database("<server>", "<database>"),218 <schema>_<table> = Source{[Schema="<schema>",Item="<table>"]}[Data]219in220 <schema>_<table>221```222223The connector emits one M-Code block per table into `data-model.json` → `mCodeAdapter.templates`.224225### Step 6: Mode Selection226227Set `mCodeAdapter.mode` based on source:228229| Source | Mode |230|---|---|231| Databricks, Snowflake, BigQuery, Synapse, SQL Server (large), Oracle | `directQuery` |232| Excel, CSV, Parquet, SharePoint List, REST API | `import` |233| Postgres, MySQL | `directQuery` if user opts in, otherwise `import` |234235### Step 7: Emit `openQuestions`236237Append a question for every uncertainty:238239- Ambiguous fact/dimension classification240- Inferred (not declared) foreign keys241- Multiple date columns (which is the primary date?)242- Tables with no measurable columns (skip or include?)243- Files with multiple sheets where some look like junk (e.g., metadata, instructions)244245## Outputs Handed to Downstream246247| File | Consumer | Purpose |248|---|---|---|249| `data-model.json` | `semantic-mapper`, `visual-selector`, `nlq-dashboard-orchestrator` | Normalized model |250| `data-model.json.openQuestions[]` | `nlq-dashboard-orchestrator` (Gate A) | Drive clarification dialog |251| `data-model.json.mCodeAdapter` | `semantic-mapper` | TMDL partition source blocks |252253## Validation Checklist2542551. Every table has at least one column2562. Every relationship references existing `{table, column}` on both sides2573. Every column has both `dataType` and `sourceProviderType`2584. Exactly zero or one date-dimension table is flagged as `role: "date dimension"` per date role2595. At least one table is classified as `fact`2606. `mCodeAdapter.templates` has one entry per table in `tables[]`2617. `openQuestions` is empty OR every entry has a unique `id` and a non-empty `question`262263## Error Handling264265| Error | Resolution |266|---|---|267| Cannot reach source | Surface error verbatim; ask user for corrected connection params |268| Authentication failed | Ask user for credentials; never store them in `data-model.json` |269| Empty schema (no tables found) | Stop and ask the user to verify scope |270| Table with zero columns | Skip and log a warning |271| Source type unsupported | Ask user to convert to a supported source (e.g., export DB query to CSV) |272273## Resources274275- **`scripts/introspect_source.py`** — Main connector entry point; routes to source-specific adapters276- **`references/connection-patterns.md`** — Per-source connection recipes and M-Code templates277- **`references/clarification-questions.md`** — Standard question bank for missing/ambiguous inputs278- **`references/data-model-schema.md`** — Full JSON schema for `data-model.json`