mamrez

An AI copilot that is not allowed to guess

Adding language-model features to a Power BI-style dashboard builder, where every column the model names is checked against the data model before anything runs.

By · Farakav dashboard builder, analytics on SSAS · Golrang System · 2025 to present

  • React
  • TypeScript
  • C# / ASP.NET Core
  • EF Core
  • SSAS / DAX
  • Claude and OpenAI APIs
The model that sounds sure of itself.
4AI features on one model-agnostic gateway

Outcome

  • Every column or measure a model proposes is checked against the real data model before anything runs
  • Claude, OpenAI and an internal model behind one gateway, with fallback and usage tracking
  • Sixteen visual types on one shared query engine, covered by 571 backend tests

The problem

Farakav’s dashboard builder lets analysts build Power BI-style reports on SSAS models. I am rewriting it in phases: a React and TypeScript front end with sixteen visual types on a drag-and-drop canvas, and an ASP.NET Core service that reads the model’s metadata, generates DAX and runs the queries.

The brief also asked for AI: describe a chart or a measure, in Persian or English, and get it built. A language model will happily produce a plausible column name that does not exist. In a reporting tool that is worse than an error, because people trust the chart that comes out of it.

What I did

The model never writes a query and never reaches SSAS. In the dashboard copilot it can do three things: propose a query definition, propose a DAX measure, or ask a clarifying question. Its prompt carries the model’s real metadata, with hidden fields left out.

Every column and measure id it proposes is checked against that metadata before the proposal reaches the screen. If one does not exist, the proposal becomes a clarifying question. An accepted proposal runs through the same query engine and measure validation as a hand-built visual, so the AI adds no second path to the data.

Later features follow the same rule: a natural-language measure builder, a DAX copilot that explains, repairs and generates expressions, and an analyst that answers questions about a dashboard by running validated queries. All of them call one AI gateway that routes by task and data-sensitivity policy, falls back to a second provider when the first fails, and logs usage and cost. Moving between Claude, OpenAI and the company’s internal model is a configuration change.

The back end was new to me. I came to it as a TypeScript developer and wrote the C# with an AI coding agent, reviewing each phase, checking it against a real SSAS model, and keeping the reasoning behind each design decision in the repository’s docs.

Every proposalA questionin Persian or EnglishModel proposescolumn and measure idsMetadata checkdoes every id exist?tools onlyOnly thenAll ids existshown as a previewAnalyst approvesnothing runs beforeQuery enginesame path as any visualapprove
The model only proposes. Its proposal is checked against the real metadata, and an id that does not exist turns it into a clarifying question. An approved proposal runs through the same query engine as a visual built by hand.

What I would do differently

The first version of the AI gateway was built from a summary of the requirements instead of the specification itself, and it came out as a third hard-coded provider. The real spec asked for a model-agnostic gateway, so that work was redone. I now read the source document before building, however clear the summary sounds.

I wrote about the design behind this in more depth: Letting a language model build dashboards without trusting it.