{"id":224,"date":"2026-10-26T20:20:27","date_gmt":"2026-10-26T20:20:27","guid":{"rendered":"https:\/\/nils-becker.org\/?p=224"},"modified":"2026-10-26T20:20:29","modified_gmt":"2026-10-26T20:20:29","slug":"one-semantic-model-many-platforms-power-bi-to-snowflake-databricks-co","status":"publish","type":"post","link":"https:\/\/nils-becker.org\/?p=224&lang=en","title":{"rendered":"One Semantic Model, Many Platforms: Power BI to Snowflake, Databricks &amp; Co."},"content":{"rendered":"\n<p>For many companies, Power BI is the linchpin when it comes to figures, facts and decisions. It is where the meaning of the data is described, where data is transformed and where its relationships are modelled. However, this information often remains isolated within the Microsoft universe. If you want to use agents to generate insights, you first have to go to considerable effort to make it available to them. Often, the semantics are rebuilt by hand.<\/p>\n\n\n\n<p>So I wrote a small tool: <a href=\"https:\/\/github.com\/s6nlbeck\/tmdl2ossie\">https:\/\/github.com\/s6nlbeck\/tmdl2ossie<\/a>. It reads a Power BI semantic model and converts it into the vendor-neutral format \u201cApache Ossie\u201d. From there, it exports to Snowflake, Databricks, Cube and Honeydew.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The problem: every semantic layer speaks its own language<\/h2>\n\n\n\n<p>Semantic layers are everywhere at the moment. Snowflake has semantic models for Cortex Analyst, Databricks has Metric Views, and then there are Cube, Honeydew, dbt and others. At their core, they all describe the same things: tables, fields, relationships and metrics. And every platform uses its own format to do so.<\/p>\n\n\n\n<p>This is particularly frustrating because AI agents need exactly these semantics to reduce hallucinations and give reliable answers about the data. Without them, they don\u2019t know that \u201crevenue\u201d comes from the sales table and is linked to the region via the customer ID.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Apache Ossie as a common language<\/h2>\n\n\n\n<p>Apache Ossie is a vendor-neutral YAML format for exactly this kind of description. The idea is simple: translate into Ossie once, or model directly in Ossie, then export anywhere. A number of ready-made converters are already available for exporting.<\/p>\n\n\n\n<p>The catch: for many companies, Power BI is <em>the<\/em> semantic tool. Maintaining the semantics twice is no solution. Unfortunately, neither is retiring Power BI. Data analysts know and value their Power BI, and switching involves a great deal of effort.<\/p>\n\n\n\n<p>Since Power BI doesn\u2019t export Ossie itself, I needed a solution that keeps Power BI in use but ensures the semantics don\u2019t have to be laboriously rebuilt. My goal: the semantics are maintained in Power BI and can also be used in Snowflake and the like with little effort.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What it looks like<\/h2>\n\n\n\n<p>The interface is a small web app with four tabs: upload a Power BI project as a ZIP file, review and edit the translated semantics, view the data model as a graph, and export for the target platform. Everything runs locally; no connection to Power BI or a data warehouse is required.<\/p>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex\">\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"507\" data-id=\"225\" src=\"https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/databricks_semantic-1-1024x507.png\" alt=\"\" class=\"wp-image-225\" srcset=\"https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/databricks_semantic-1-1024x507.png 1024w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/databricks_semantic-1-300x148.png 300w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/databricks_semantic-1-768x380.png 768w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/databricks_semantic-1-1536x760.png 1536w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/databricks_semantic-1-2048x1013.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"512\" data-id=\"226\" src=\"https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/editor-1-1024x512.png\" alt=\"\" class=\"wp-image-226\" srcset=\"https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/editor-1-1024x512.png 1024w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/editor-1-300x150.png 300w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/editor-1-768x384.png 768w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/editor-1-1536x767.png 1536w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/editor-1-2048x1023.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"512\" data-id=\"227\" src=\"https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/export-1-1024x512.png\" alt=\"\" class=\"wp-image-227\" srcset=\"https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/export-1-1024x512.png 1024w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/export-1-300x150.png 300w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/export-1-768x384.png 768w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/export-1-1536x767.png 1536w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/export-1-2048x1023.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"512\" data-id=\"228\" src=\"https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/graph-1-1024x512.png\" alt=\"\" class=\"wp-image-228\" srcset=\"https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/graph-1-1024x512.png 1024w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/graph-1-300x150.png 300w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/graph-1-768x384.png 768w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/graph-1-1536x767.png 1536w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/graph-1-2048x1023.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"512\" data-id=\"229\" src=\"https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/upload-1-1024x512.png\" alt=\"\" class=\"wp-image-229\" srcset=\"https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/upload-1-1024x512.png 1024w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/upload-1-300x150.png 300w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/upload-1-768x384.png 768w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/upload-1-1536x767.png 1536w, https:\/\/nils-becker.org\/wp-content\/uploads\/2026\/10\/upload-1-2048x1023.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n<\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Under the bonnet<\/h2>\n\n\n\n<p>My tool uses the TMDL files that Power BI creates when you save a model as a project (.pbip). In the first step, a purpose-built TMDL parser reads this proprietary Microsoft format \u2013 pure Python, no dependencies.<\/p>\n\n\n\n<p>The objects from the TMDL files are then mapped to Ossie semantics. The whole process is static and deterministic: the same model always produces the same result.<\/p>\n\n\n\n<p>The Ossie model can then be processed further using one of the ready-made converters. I have integrated the ones for Snowflake, Cube and Honeydew directly into my project. The Databricks converter, unfortunately, is written in Java. So that the tool can do without Java, I ported it to Python. The original\u2019s tests also run against the port, and the output is identical to that of the Java converter.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Limitations<\/h2>\n\n\n\n<p>So far, the tool has been tested with a small sample model and the converters\u2019 test suites, not with large production models. The main open issue is translating DAX into SQL. DAX measures are carried over, but SQL-based targets such as Snowflake or Databricks skip them. The obvious solution would be an AI agent that translates DAX into SQL \u2013 but that would sacrifice determinism.<\/p>\n\n\n\n<p>If you have a larger model and give it a try, I\u2019d be glad to hear your feedback and see issues on GitHub.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Licence<\/h2>\n\n\n\n<p>tmdl2ossie is free for private use, research, teaching and non-profit organisations (PolyForm Noncommercial 1.0.0). A commercial licence is available for use in companies; please send enquiries to mail(at)nils-becker.org.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>For many companies, Power BI is the linchpin when it comes to figures, facts and decisions. It is where the meaning of the data is described, where data is transformed and where its relationships are modelled. However, this information often remains isolated within the Microsoft universe. If you want to use agents to generate insights, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11],"tags":[44,46,39,32,34,42],"class_list":["post-224","post","type-post","status-publish","format-standard","hentry","category-nicht-kategorisiert-en","tag-databricks","tag-honeydew","tag-powerbi","tag-semantic","tag-semantic-layer","tag-snowflake"],"_links":{"self":[{"href":"https:\/\/nils-becker.org\/index.php?rest_route=\/wp\/v2\/posts\/224","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nils-becker.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nils-becker.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nils-becker.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nils-becker.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=224"}],"version-history":[{"count":1,"href":"https:\/\/nils-becker.org\/index.php?rest_route=\/wp\/v2\/posts\/224\/revisions"}],"predecessor-version":[{"id":230,"href":"https:\/\/nils-becker.org\/index.php?rest_route=\/wp\/v2\/posts\/224\/revisions\/230"}],"wp:attachment":[{"href":"https:\/\/nils-becker.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=224"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nils-becker.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=224"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nils-becker.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=224"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}