agentleFS
MicrosoftKnown publishermicrosoft/fabric-apps-analytic-templates/.agents/skills/semantic-model-data/SKILL.md

semantic-model-data

Skill29 starsChanged 10 days agoMITView on GitHub

How the app reads semantic model data at runtime: useSemanticModelQuery, its normalized result and error shape, error categories and overflow, bounding rows, and the app-level query cache.

The file

We split this skill into the rules it gives an agent

3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use the hook. It caches and never throws: every failure, whether the connector threw or Power BI returned an error, is normalized into the …

    line 19
  2. Very specific:

    Do not branch on dataType.

    line 75
  3. Distinctive:

    Every failure, thrown or returned, carries one of these categories:

    line 94

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Semantic Model Data at Runtime

Overview

The app runs DAX against a semantic model through a connector registered in rayfin/rayfin.yml (see AGENTS.md). The app sends only the query; the connector's workspaceId and itemId are injected server-side.

Quick Start

Use the hook. It caches and never throws: every failure, whether the connector threw or Power BI returned an error, is normalized into the same result shape and arrives as data.status === "error".

import { useSemanticModelQuery } from "@/hooks/use-semantic-model-query";

const { data, isLoading, error, refetch } = useSemanticModelQuery({
  connection: "sales",
  query: 'EVALUATE SUMMARIZECOLUMNS(Product[Category], "Total", [Sales Amount])',
});

if (data?.status === "success") {
  // data.table.columns — one entry per output column
  // data.table.rows    — unknown[][], aligned with columns by index (frozen; copy before sorting)
} else if (data?.status === "error") {
  // data.error.category — see Error Categories
  // data.error.message
}

error mirrors data.error.message as a convenience for code that just wants a string. It is never the only signal, so branching on status is enough.

The hook's options, result type, and error normalization are in src/hooks/use-semantic-model-query.ts; read it when you need more than this skill covers. Calling getRayfinClient().connectors.<name>.executeQuery() directly skips both that normalization (on the deployed path it can throw) and the cache, so use the hook.

Key Concepts

Querying

Each query must contain exactly one EVALUATE statement.

Bounding rows

There is no default row cap, so bound the DAX with an aggregation or TOPN(...).

The connector's executeQuery also accepts an optional resultSetRowCountLimit alongside query. It silently truncates: the query succeeds with at most that many rows and no error or flag. It is not a completeness guard. If you use it, treat a result with exactly resultSetRowCountLimit rows as possibly incomplete.

useSemanticModelQuery does not forward the field, so from app code shape the DAX itself.

Result Handling

On success, data.table holds the result:

  • rows line up with columns by index.
  • Each column name (for example Product[Name] or [Sales]) is that column's columnMetadata key. Take it from the query's actual output.
  • Do not branch on dataType. It is a wire type such as Int64, or "unknown" when the response carried no column metadata, not a display type. Supply display types through columnMetadata and toDataTable.

Without column metadata, names are inferred from the first row, so a column missing from the first row (or a result with no rows) yields no column. Shape the DAX so every column is present in the first row.

Caching

The connectors client does not cache. Caching is an app-level concern and lives in src/lib/query-cache.ts, above the query hooks, so one store serves every source kind. Use useSemanticModelQuery and you get it for free.

Successful results and DAX (query) errors are cached. api, network, overflow, and unknown errors are not, since retrying may succeed.

Error Categories

Every failure, thrown or returned, carries one of these categories:

CategoryMeaningWhat to do
queryPower BI ran the query and rejected it, typically invalid DAXFix the DAX
overflowA value cannot be represented as a JavaScript numberSee below
networkNo response arrived (offline, DNS, CORS)Retry
apiThe service answered with a failure, typically auth or permissionsCheck sign-in and access
unknownAnything elseRead message

overflow is an error result with no table: nothing partial comes back to render. It means a value in the result cannot be turned into a JavaScript number without losing precision, so the connector refuses the whole result:

  • an Int64 beyond Number.MAX_SAFE_INTEGER (±9,007,199,254,740,991), or
  • a fixed-decimal value whose unscaled digits exceed it. Power BI's fixed-decimal type carries four decimal places, so its limit is about ±900 billion (±900,719,925,474.0991).

Row limits and TOPN do not help, and the CLI's generic recovery text ("Lower resultSetRowCountLimit, or narrow the query") does not apply to this case. Change the value in DAX: convert it to text (FORMAT([Measure], "0.00") or CONVERT([Measure], STRING)) and treat it as a string, convert it to a floating-point number (CONVERT([Measure], DOUBLE)) if approximate values are acceptable, or scale it down (for example report thousands or millions).

Too many rows is not an overflow: resultSetRowCountLimit truncates silently (see Bounding rows).

Common Patterns

Multiple models

const sales = useSemanticModelQuery({ connection: "sales", query: salesDax });
const inventory = useSemanticModelQuery({ connection: "inventory", query: invDax });

Forcing fresh data

const { data, refetch } = useSemanticModelQuery({ connection: "sales", query });
await refetch();                       // skips the cache

clearQueryCache() empties the store but does not re-render anything already mounted — it changes what the next query reads. Use refetch() to re-read now.

Anti-Patterns

  • Don't branch on dataType — it is a wire type (or "unknown" when column metadata was absent and names had to be inferred from the first row), not a display type.
  • Don't treat a resolved promise as success — check status.
  • Don't send workspaceId or itemId — they come from rayfin.yml server-side.
  • Don't build your own cache — use useSemanticModelQuery.
  • Don't return unbounded results — there is no default row cap, and the cache retains up to 256 results for the session. Bound the DAX with an aggregation or TOPN(...).
  • Don't mutate a result — tables are frozen. Copy before sorting.

Type Reference

useSemanticModelQuery returns data as CachedQueryResult<SemanticModelQueryResult>: the connector's result union plus the fromCache and cachedAt fields the app cache adds (src/lib/query-cache.ts). Import these types rather than redefining them.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.