agentsclimarketplace

Visual generator

Skill bcastelino/powerbi-dashboard-generator/skills/visual-generator

Agent Skills toolkit that turns plain-English requests into fully-formed, branded Power BI Desktop Projects (PBIP). Ten composable skills for source-agnostic semantic modeling, visual generation, and theming.

Install
npx -y skills add bcastelino/powerbi-dashboard-generator --skill visual-generator

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Builds Power BI report visual JSON files in PBIR format, where each visual is a separate visual.json file inside its own directory. Also generates page.json, pages.json, report.json, version.json, and definition.pbir. Use this skill when generating the report layer of a PBIP project from a selected visual type and field mappings. This is Stage 3 of the query-to-pbip pipeline.

SKILL.md

35.8 KB, ~9.7k tokens by cl100k_base, as published. Nobody here has run it

Visual Generator

Build Power BI report visual definitions in PBIR (Power BI Enhanced Report) format. In PBIR, each visual is stored as a separate visual.json file inside its own directory under pages/<pageId>/visuals/<visualId>/. This skill takes a visual type and field-to-bucket mapping from the visual selector and produces all report files.

When to Use This Skill

  • Generating individual visual.json files for a PBIP report
  • Building visual containers with semantic query bindings
  • Creating report definition files (report.json, version.json, pages.json)
  • Producing definition.pbir pointer files linking report to semantic model
  • Constructing visuals as part of the query-to-pbip pipeline (Stage 3)

Inputs

  • visualType — The PBIR visual type string from Stage 2 (e.g., cardVisual, clusteredColumnChart)
  • bucketMapping — Field-to-bucket assignments from Stage 2
  • tableNames — Table and column/measure names from Stage 1
  • projectName — Name of the PBIP project (for definition.pbir path references)
  • pageLayout (optional) — Page dimensions; defaults to 1280x720

Outputs

FileLocationPurpose
visual.jsondefinition/pages/<pageId>/visuals/<visualId>/One file per visual (schema 2.5.0)
page.jsondefinition/pages/<pageId>/Page definition (schema 2.0.0)
pages.jsondefinition/pages/Page ordering metadata
report.jsondefinition/Report-level configuration (schema 3.1.0)
version.jsondefinition/Report format version
definition.pbir<ProjectName>.Report/Pointer to semantic model

Visual Generation Workflow

Step 1: Build the Semantic Query

For each field in the bucket mapping, construct the appropriate field reference. The agent should use the available tables, models, and relationships from Stage 1 to determine the best way to display the visual — leveraging actual column types, sort orders, and relationships defined in the semantic model.

Column reference (for dimensions):

{
  "field": {
    "Column": {
      "Expression": { "SourceRef": { "Entity": "<TableName>" } },
      "Property": "<ColumnName>"
    }
  },
  "queryRef": "<TableName>.<ColumnName>",
  "nativeQueryRef": "<ColumnName>",
  "active": true
}

Measure reference (for aggregated values):

{
  "field": {
    "Measure": {
      "Expression": { "SourceRef": { "Entity": "<FactTableName>" } },
      "Property": "<MeasureName>"
    }
  },
  "queryRef": "<FactTableName>.<MeasureName>",
  "nativeQueryRef": "<MeasureName>"
}

The nativeQueryRef is typically just the column or measure name without the table prefix.

Step 2: Select and Populate the Visual Template

Load the appropriate template from assets/visual-templates/ in the query-to-pbip skill and replace placeholders:

TemplateVisual TypePlaceholders
cardVisual.jsoncardVisual{{VisualName}}, {{MeasureTable}}, {{MeasureName}}
clusteredColumnChart.jsonclusteredColumnChart{{VisualName}}, {{CategoryTable}}, {{CategoryColumn}}, {{MeasureTable}}, {{MeasureName}}, optional {{SeriesTable}}, {{SeriesColumn}}
clusteredBarChart.jsonclusteredBarChart{{VisualName}}, {{CategoryTable}}, {{CategoryColumn}}, {{MeasureTable}}, {{MeasureName}}, optional {{SeriesTable}}, {{SeriesColumn}}
lineChart.jsonlineChart{{VisualName}}, {{CategoryTable}}, {{CategoryColumn}}, {{MeasureTable}}, {{MeasureName}}
tableEx.jsontableEx{{VisualName}}, {{Columns}}
slicer.jsonslicer{{VisualName}}, {{SlicerTable}}, {{SlicerColumn}}

Replace {{VisualName}} with a 20-character hex identifier (e.g., uuid.uuid4().hex[:20]).

When Stage 1 includes derived categorical aliases (for example a SQL CASE alias), populate the Series bucket for supported visuals (clusteredColumnChart, clusteredBarChart, lineChart) so Power BI renders legend-based color splits.

Step 3: Set Visual Positioning

Apply position coordinates within the visual.json:

Single full-page visual:

{ "x": 20, "y": 20, "z": 0, "height": 680, "width": 1240, "tabOrder": 0 }

Single centered card:

{ "x": 440, "y": 260, "z": 0, "height": 200, "width": 400, "tabOrder": 0 }

Multi-card header row (up to 4):

Card #xyheightwidth
12020150300
234020150300
366020150300
498020150280

Chart with slicer:

ElementxyheightwidthztabOrder
Slicer0011011040004000
Chart1100680117000

Step 4: Write Each Visual as a Separate File

In PBIR format, each visual gets its own directory and visual.json file:

definition/pages/<pageId>/visuals/
├── <visualId1>/
│   └── visual.json
├── <visualId2>/
│   └── visual.json
└── <visualId3>/
    └── visual.json

Generate a unique 20-character hex ID for each visual: uuid.uuid4().hex[:20]

Step 5: Assemble page.json

The page definition does NOT contain visual definitions (those are in separate files):

{
  "$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/page/2.0.0/schema.json",
  "name": "<pageId>",
  "displayName": "<Page Title>",
  "displayOption": "FitToPage",
  "height": 720,
  "width": 1280
}

Set displayName to a descriptive title derived from the query (e.g., "Revenue by State"). Generate name as a 20-character hex ID: uuid.uuid4().hex[:20]

Step 6: Generate pages.json

{
  "$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/pagesMetadata/1.0.0/schema.json",
  "pageOrder": ["<pageId>"],
  "activePageName": "<pageId>"
}

Step 7: Generate report.json

Located at definition/report.json:

{
  "$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/report/3.1.0/schema.json",
  "themeCollection": {
    "baseTheme": {
      "name": "CY25SU11",
      "reportVersionAtImport": {
        "visual": "2.4.0",
        "report": "3.0.0",
        "page": "2.3.0"
      },
      "type": "SharedResources"
    }
  },
  "resourcePackages": [
    {
      "name": "SharedResources",
      "type": "SharedResources",
      "items": [
        {
          "name": "CY25SU11",
          "path": "BaseThemes/CY25SU11.json",
          "type": "BaseTheme"
        }
      ]
    }
  ],
  "settings": {
    "useStylableVisualContainerHeader": true,
    "exportDataMode": "AllowSummarized",
    "defaultDrillFilterOtherVisuals": true,
    "allowChangeFilterTypes": true,
    "useEnhancedTooltips": true,
    "useDefaultAggregateDisplayName": true
  }
}

Step 8: Generate version.json

{
  "$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/versionMetadata/1.0.0/schema.json",
  "version": "2.0.0"
}

Step 9: Generate definition.pbir

{
  "$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definitionProperties/2.0.0/schema.json",
  "version": "4.0",
  "datasetReference": {
    "byPath": {
      "path": "../<ProjectName>.SemanticModel"
    }
  }
}

Visual Container Structure (visual.json)

Every visual uses schema version 2.5.0:

{
  "$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/2.5.0/schema.json",
  "name": "<20-char-hex-id>",
  "position": {
    "x": 0, "y": 0, "z": 0,
    "height": 0, "width": 0,
    "tabOrder": 0
  },
  "visual": {
    "visualType": "<type>",
    "query": {
      "queryState": {
        "<BucketName>": {
          "projections": [ ... field references ... ]
        }
      },
      "sortDefinition": {
        "sort": [
          {
            "field": { ... field reference (Column or Measure) ... },
            "direction": "Ascending" or "Descending"
          }
        ],
        "isDefaultSort": true
      }
    },
    "drillFilterOtherVisuals": true
  }
}

sortDefinition

The sortDefinition object controls the default sort order for the visual. Always include a sortDefinition in the visual->query JSON to improve the visual presentation. The sort field reference uses the same structure as projection field references.

Sort direction values: "Ascending" or "Descending"

Guidelines for choosing sort:

  • Line charts with temporal categories (year, month, date): Sort by the temporal column Ascending so data flows chronologically left-to-right. Add both year and month columns as Category projections when the query involves monthly trends.
  • Column/bar charts with temporal categories: Sort by the temporal column Ascending.
  • Column/bar charts with nominal categories (state, product, etc.): Sort by the measure Descending to show highest values first.
  • Card visuals: Sort by the measure Descending.
  • Table visuals: Sort by the first measure column Descending.

Example — Line chart with year sort:

"sortDefinition": {
  "sort": [
    {
      "field": {
        "Column": {
          "Expression": { "SourceRef": { "Entity": "dim_date" } },
          "Property": "year"
        }
      },
      "direction": "Ascending"
    }
  ],
  "isDefaultSort": true
}

Example — Column chart sorted by measure descending:

"sortDefinition": {
  "sort": [
    {
      "field": {
        "Measure": {
          "Expression": { "SourceRef": { "Entity": "fact_sales" } },
          "Property": "Total Revenue (GMV)"
        }
      },
      "direction": "Descending"
    }
  ],
  "isDefaultSort": true
}

Temporal Projections for Line Charts

When the genie query involves time-series data (e.g., revenue over time, monthly trends), include multiple temporal columns as Category projections to enable drill-down and proper axis formatting:

"Category": {
  "projections": [
    {
      "field": {
        "Column": {
          "Expression": { "SourceRef": { "Entity": "dim_date" } },
          "Property": "year"
        }
      },
      "queryRef": "dim_date.year",
      "nativeQueryRef": "year",
      "active": true
    },
    {
      "field": {
        "Column": {
          "Expression": { "SourceRef": { "Entity": "dim_date" } },
          "Property": "month"
        }
      },
      "queryRef": "dim_date.month",
      "nativeQueryRef": "month",
      "active": true
    }
  ]
}

This adds both year and month to the category axis, enabling the user to see monthly breakdowns within each year.


## Visual Type to PBIR Mapping

| Display Name | PBIR `visualType` | Data Bucket |
|---|---|---|
| Card (new) | `cardVisual` | `Data` |
| Clustered Column Chart | `clusteredColumnChart` | `Category`, `Y`, `Series` |
| Line Chart | `lineChart` | `Category`, `Y`, `Series` |
| Combo Chart | `lineClusteredColumnComboChart` | `Category`, `Y`, `Y2` |
| Table | `tableEx` | `Values` |
| Matrix | `pivotTable` | `Rows`, `Columns`, `Values` |
| Slicer | `slicer` | `Values` |
| Filled Map | `filledMap` | `Category`, `Size` |
| Pie Chart | `pieChart` | `Category`, `Y` |
| Donut Chart | `donutChart` | `Category`, `Y` |
| Clustered Bar Chart | `clusteredBarChart` | `Category`, `Y`, `Series` |
| Scatter Chart | `scatterChart` | `X`, `Y`, `Size` |

## Z-Order and Tab Order

- Data visuals: z = 0, tabOrder = 0 (increment by 1000 for layering)
- Combo/overlay visuals: z = 3000, tabOrder = 3000
- Slicers: z = 4000, tabOrder = 4000

## Visual Objects Reference

The `visual.objects` property controls formatting and behavior for each visual type. The `visualContainerObjects` property controls container-level formatting (title, background, border) and applies to ALL visual types.

### Value Encoding

All values inside `objects` use expression literals. Encoding rules:

| Type | Encoding | Example |
|---|---|---|
| String | Wrapped in single quotes | `"'Top'"`, `"'Dropdown'"` |
| Number | Suffixed with `D` | `"28D"`, `"0D"` |
| Boolean | Lowercase string | `"true"`, `"false"` |
| Long integer | Suffixed with `L` | `"40L"` |

### General Structure

```json
"objects": {
  "propertyName": [
    {
      "properties": {
        "subProperty": {
          "expr": {
            "Literal": {
              "Value": "'value'"
            }
          }
        }
      }
    }
  ]
}

cardVisual

Property GroupSub-PropertyTypeDescription
labelsfontSizeNumberFont size for the value label (e.g., "28D")
labelslabelDisplayUnitsNumberDisplay units: 0D = Auto, 1D = None, 1000D = Thousands, 1000000D = Millions
labelscolorStringHex color for label text
categoryLabelsshowBooleanShow/hide category label below the value
categoryLabelsfontSizeNumberFont size for the category label

Example — Card with fontSize 28, auto display units:

"objects": {
  "labels": [
    {
      "properties": {
        "fontSize": {
          "expr": { "Literal": { "Value": "28D" } }
        },
        "labelDisplayUnits": {
          "expr": { "Literal": { "Value": "0D" } }
        }
      }
    }
  ],
  "categoryLabels": [
    {
      "properties": {
        "show": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "fontSize": {
          "expr": { "Literal": { "Value": "12D" } }
        }
      }
    }
  ]
}

clusteredColumnChart

Property GroupSub-PropertyTypeDescription
categoryAxisshowBooleanShow/hide category axis
categoryAxislabelAngleNumberAxis label rotation in degrees
categoryAxisfontSizeNumberAxis label font size
valueAxisshowBooleanShow/hide value axis
valueAxisgridlineShowBooleanShow/hide horizontal gridlines
valueAxislabelDisplayUnitsNumberDisplay units for axis labels
labelsshowBooleanShow/hide data labels on bars
labelslabelDisplayUnitsNumberDisplay units for data labels
labelsfontSizeNumberData label font size
labelscolorStringData label color
legendshowBooleanShow/hide legend
legendpositionStringLegend position: 'Top', 'Bottom', 'Left', 'Right'
legendfontSizeNumberLegend font size
dataPointfillColorPer-series color (requires selector)

Example — Column chart with visible axes, data labels off, legend on top:

"objects": {
  "categoryAxis": [
    {
      "properties": {
        "show": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "labelAngle": {
          "expr": { "Literal": { "Value": "0D" } }
        },
        "fontSize": {
          "expr": { "Literal": { "Value": "9D" } }
        }
      }
    }
  ],
  "valueAxis": [
    {
      "properties": {
        "show": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "gridlineShow": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "labelDisplayUnits": {
          "expr": { "Literal": { "Value": "0D" } }
        }
      }
    }
  ],
  "labels": [
    {
      "properties": {
        "show": {
          "expr": { "Literal": { "Value": "false" } }
        }
      }
    }
  ],
  "legend": [
    {
      "properties": {
        "show": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "position": {
          "expr": { "Literal": { "Value": "'Top'" } }
        },
        "fontSize": {
          "expr": { "Literal": { "Value": "9D" } }
        }
      }
    }
  ]
}

Per-series color using selector pattern:

"dataPoint": [
  {
    "properties": {
      "fill": {
        "solid": {
          "color": {
            "expr": { "Literal": { "Value": "'#19F5E2'" } }
          }
        }
      }
    },
    "selector": {
      "metadata": "fact_sales.Total Revenue (GMV)"
    }
  }
]

lineChart

Property GroupSub-PropertyTypeDescription
categoryAxisshowBooleanShow/hide category axis
categoryAxislabelAngleNumberAxis label rotation in degrees
categoryAxisfontSizeNumberAxis label font size
valueAxisshowBooleanShow/hide value axis
valueAxisgridlineShowBooleanShow/hide horizontal gridlines
valueAxislabelDisplayUnitsNumberDisplay units for axis labels
lineStylesshowMarkerBooleanShow data point markers on lines
lineStylesstrokeWidthNumberLine thickness in pixels
lineStylesmarkerShapeStringMarker shape: 'circle', 'square', 'diamond', 'triangle', 'cross', 'pentagon', 'hexagon'
labelsshowBooleanShow/hide data labels
labelslabelDisplayUnitsNumberDisplay units for data labels
labelsfontSizeNumberData label font size

Example — Line chart with markers enabled, stroke width 2px:

"objects": {
  "lineStyles": [
    {
      "properties": {
        "showMarker": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "strokeWidth": {
          "expr": { "Literal": { "Value": "2D" } }
        },
        "markerShape": {
          "expr": { "Literal": { "Value": "'circle'" } }
        }
      }
    }
  ],
  "categoryAxis": [
    {
      "properties": {
        "show": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "fontSize": {
          "expr": { "Literal": { "Value": "9D" } }
        }
      }
    }
  ],
  "valueAxis": [
    {
      "properties": {
        "show": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "gridlineShow": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "labelDisplayUnits": {
          "expr": { "Literal": { "Value": "0D" } }
        }
      }
    }
  ],
  "labels": [
    {
      "properties": {
        "show": {
          "expr": { "Literal": { "Value": "false" } }
        }
      }
    }
  ]
}

slicer

Property GroupSub-PropertyTypeDescription
datamodeStringSlicer mode: 'Dropdown', 'List', 'Between'
datacanSelectAllBooleanShow "Select All" option
generalorientationStringLayout orientation: 'Vertical', 'Horizontal'
selectionsingleSelectBooleanRestrict to single selection

Example — Dropdown slicer with single select:

"objects": {
  "data": [
    {
      "properties": {
        "mode": {
          "expr": { "Literal": { "Value": "'Dropdown'" } }
        },
        "canSelectAll": {
          "expr": { "Literal": { "Value": "true" } }
        }
      }
    }
  ],
  "general": [
    {
      "properties": {
        "orientation": {
          "expr": { "Literal": { "Value": "'Vertical'" } }
        }
      }
    }
  ],
  "selection": [
    {
      "properties": {
        "singleSelect": {
          "expr": { "Literal": { "Value": "true" } }
        }
      }
    }
  ]
}

Example — Date range slicer (Between mode):

"objects": {
  "data": [
    {
      "properties": {
        "mode": {
          "expr": { "Literal": { "Value": "'Between'" } }
        }
      }
    }
  ]
}

tableEx

Property GroupSub-PropertyTypeDescription
valuesfontSizeNumberCell font size
valuesfontFamilyStringCell font family (e.g., 'Segoe UI')
columnHeadersfontSizeNumberHeader font size
columnHeadersfontFamilyStringHeader font family
columnHeadersboldBooleanBold header text
gridgridVerticalBooleanShow vertical grid lines
gridgridHorizontalBooleanShow horizontal grid lines
gridrowPaddingNumberVertical padding per row in pixels

Example — Table with grid lines and custom font sizing:

"objects": {
  "values": [
    {
      "properties": {
        "fontSize": {
          "expr": { "Literal": { "Value": "10D" } }
        },
        "fontFamily": {
          "expr": { "Literal": { "Value": "'Segoe UI'" } }
        }
      }
    }
  ],
  "columnHeaders": [
    {
      "properties": {
        "fontSize": {
          "expr": { "Literal": { "Value": "11D" } }
        },
        "fontFamily": {
          "expr": { "Literal": { "Value": "'Segoe UI'" } }
        },
        "bold": {
          "expr": { "Literal": { "Value": "true" } }
        }
      }
    }
  ],
  "grid": [
    {
      "properties": {
        "gridVertical": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "gridHorizontal": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "rowPadding": {
          "expr": { "Literal": { "Value": "4D" } }
        }
      }
    }
  ]
}

visualContainerObjects (All Visual Types)

The visualContainerObjects property is set at the same level as visual.objects but controls the visual container chrome (title bar, background, border, header). These apply to every visual type.

Property GroupSub-PropertyTypeDescription
titleshowBooleanShow/hide visual title
titletextStringTitle text content
titlefontSizeNumberTitle font size
titlefontColorStringTitle text color (hex)
titlealignmentStringTitle alignment: 'Left', 'Center', 'Right'
backgroundshowBooleanShow/hide background fill
backgroundcolorStringBackground color (hex)
backgroundtransparencyNumberBackground transparency (0–100)
bordershowBooleanShow/hide border
bordercolorStringBorder color (hex)
borderradiusNumberBorder corner radius in pixels
visualHeadershowBooleanShow/hide visual header icons

Example — Visual with title, background, and border:

"visualContainerObjects": {
  "title": [
    {
      "properties": {
        "show": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "text": {
          "expr": { "Literal": { "Value": "'Total Revenue'" } }
        },
        "fontSize": {
          "expr": { "Literal": { "Value": "12D" } }
        },
        "fontColor": {
          "expr": { "Literal": { "Value": "'#333333'" } }
        },
        "alignment": {
          "expr": { "Literal": { "Value": "'Left'" } }
        }
      }
    }
  ],
  "background": [
    {
      "properties": {
        "show": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "color": {
          "expr": { "Literal": { "Value": "'#FFFFFF'" } }
        },
        "transparency": {
          "expr": { "Literal": { "Value": "0D" } }
        }
      }
    }
  ],
  "border": [
    {
      "properties": {
        "show": {
          "expr": { "Literal": { "Value": "true" } }
        },
        "color": {
          "expr": { "Literal": { "Value": "'#E0E0E0'" } }
        },
        "radius": {
          "expr": { "Literal": { "Value": "4D" } }
        }
      }
    }
  ],
  "visualHeader": [
    {
      "properties": {
        "show": {
          "expr": { "Literal": { "Value": "false" } }
        }
      }
    }
  ]
}

filterConfig

The filterConfig property defines per-visual filters. It sits at the same level as visual inside the visual container. Each filter entry requires a unique name (20-character hex string), a field reference, and a type.

Filter types:

TypeUse Case
CategoricalFilters on column values (dimension fields)
AdvancedFilters on measure values (numeric comparisons)
Advanced (date range)Filters on date columns using And + DateSpan for date range (>= start, <= end)
RelativeDateFilters on date columns using Now / DateAdd expressions for rolling current-date windows

Example — Categorical filter on a column:

"filterConfig": {
  "filters": [
    {
      "name": "a1b2c3d4e5f678901234",
      "field": {
        "Column": {
          "Expression": { "SourceRef": { "Entity": "dim_date" } },
          "Property": "year"
        }
      },
      "type": "Categorical"
    }
  ]
}

Example — Advanced filter on a measure:

"filterConfig": {
  "filters": [
    {
      "name": "b2c3d4e5f6789012345a",
      "field": {
        "Measure": {
          "Expression": { "SourceRef": { "Entity": "fact_sales" } },
          "Property": "Total Revenue (GMV)"
        }
      },
      "type": "Advanced"
    }
  ]
}

Example — Combined filters (typical for charts):

"filterConfig": {
  "filters": [
    {
      "name": "a1b2c3d4e5f678901234",
      "field": {
        "Column": {
          "Expression": { "SourceRef": { "Entity": "dim_date" } },
          "Property": "year"
        }
      },
      "type": "Categorical"
    },
    {
      "name": "c3d4e5f67890123456ab",
      "field": {
        "Measure": {
          "Expression": { "SourceRef": { "Entity": "fact_sales" } },
          "Property": "Total Revenue (GMV)"
        }
      },
      "type": "Advanced"
    }
  ]
}

Example — Advanced date range filter (And + DateSpan):

When a SQL query contains an absolute date-level filter (e.g., FROM '2017-01-01' TO '2017-12-31'), use an Advanced filter with an And condition combining two Comparison nodes — one for >= (start date) and one for <= (end date). Each comparison wraps the date literal in a DateSpan with TimeUnit: 5 (day granularity).

ComparisonKind values:

ValueLogical MeaningDescription
0EqualThe values are identical.
1Greater ThanThe first value is strictly larger than the second.
2Greater Than or EqualThe first value is larger than or equal to the second.
3Less ThanThe first value is strictly smaller than the second.
4Less Than or EqualThe first value is smaller than or equal to the second.
5Not EqualThe values are different.

TimeUnit values:

ValueTimeUnitDescription
0NoneNo specific time unit specified.
1YearAnnual level granularity.
2QuarterThree-month period.
3MonthStandard calendar month.
4Week7-day period.
5Day24-hour period.
6HourHourly granularity.
7MinuteMinute-level granularity.

Placeholders for absolute date ranges:

  • {{DateRangeFilterGuid}} — unique 20-character hex GUID for this filter
  • {{DateRangeEntity}} — the date table name (e.g., dim_date)
  • {{DateRangeProperty}} — the date column name (e.g., date)
  • {{DateRangeAlias}} — the source alias for the date table in the From clause
  • {{DateStart}} — the start date literal (e.g., "datetime'2017-01-01T00:00:00'")
  • {{DateEnd}} — the end date literal (e.g., "datetime'2017-12-31T00:00:00'")
{
  "name": "{{DateRangeFilterGuid}}",
  "field": {
    "Column": {
      "Expression": {
        "SourceRef": {
          "Entity": "{{DateRangeEntity}}"
        }
      },
      "Property": "{{DateRangeProperty}}"
    }
  },
  "type": "Advanced",
  "filter": {
    "Version": 2,
    "From": [
      {
        "Name": "{{DateRangeAlias}}",
        "Entity": "{{DateRangeEntity}}",
        "Type": 0
      }
    ],
    "Where": [
      {
        "Condition": {
          "And": {
            "Left": {
              "Comparison": {
                "ComparisonKind": 2,
                "Left": {
                  "Column": {
                    "Expression": {
                      "SourceRef": {
                        "Source": "{{DateRangeAlias}}"
                      }
                    },
                    "Property": "{{DateRangeProperty}}"
                  }
                },
                "Right": {
                  "DateSpan": {
                    "Expression": {
                      "Literal": {
                        "Value": "{{DateStart}}"
                      }
                    },
                    "TimeUnit": 5
                  }
                }
              }
            },
            "Right": {
              "Comparison": {
                "ComparisonKind": 4,
                "Left": {
                  "Column": {
                    "Expression": {
                      "SourceRef": {
                        "Source": "{{DateRangeAlias}}"
                      }
                    },
                    "Property": "{{DateRangeProperty}}"
                  }
                },
                "Right": {
                  "DateSpan": {
                    "Expression": {
                      "Literal": {
                        "Value": "{{DateEnd}}"
                      }
                    },
                    "TimeUnit": 5
                  }
                }
              }
            }
          }
        }
      }
    ]
  }
}

The And condition goes inside Where[0].Condition — it is not a second item in the Where array. Do not include "howCreated": "User" in any filter entry.

Current-date-relative ranges (e.g., "last 12 months", "last 90 days"):

When the Genie query or user intent includes a rolling period relative to today, emit a native PBIR RelativeDate filter instead of substituting precomputed literal start/end dates.

Placeholders for native RelativeDate:

  • {{RelativeDateFilterGuid}} — unique 20-character hex GUID for this filter
  • {{RelativeDateEntity}} — the date table name (e.g., dim_date or dim_date_delivery)
  • {{RelativeDateProperty}} — the date column name (typically date)
  • {{RelativeDateAlias}} — the source alias for the date table in the From clause
  • {{RelativeDateAmount}} — rolling period size (e.g., 12)
  • {{RelativeDateTimeUnit}} — PBIR DateAdd time unit for the requested period
{
  "name": "{{RelativeDateFilterGuid}}",
  "field": {
    "Column": {
      "Expression": {
        "SourceRef": {
          "Entity": "{{RelativeDateEntity}}"
        }
      },
      "Property": "{{RelativeDateProperty}}"
    }
  },
  "type": "RelativeDate",
  "filter": {
    "Version": 2,
    "From": [
      {
        "Name": "{{RelativeDateAlias}}",
        "Entity": "{{RelativeDateEntity}}",
        "Type": 0
      }
    ],
    "Where": [
      {
        "Condition": {
          "Between": {
            "Expression": {
              "Column": {
                "Expression": {
                  "SourceRef": {
                    "Source": "{{RelativeDateAlias}}"
                  }
                },
                "Property": "{{RelativeDateProperty}}"
              }
            },
            "LowerBound": {
              "DateSpan": {
                "Expression": {
                  "DateAdd": {
                    "Expression": {
                      "DateAdd": {
                        "Expression": {
                          "Now": {}
                        },
                        "Amount": 1,
                        "TimeUnit": 0
                      }
                    },
                    "Amount": -{{RelativeDateAmount}},
                    "TimeUnit": {{RelativeDateTimeUnit}}
                  }
                },
                "TimeUnit": 0
              }
            },
            "UpperBound": {
              "DateSpan": {
                "Expression": {
                  "Now": {}
                },
                "TimeUnit": 0
              }
            }
          }
        }
      }
    ]
  }
}

Use the reference pattern from generated-reports/DeliveryDaysTrends/.../visual.json as the baseline for month windows. Treat "howCreated": "User" as a Desktop-authored artifact, not a required generated property.

Choose the relative-date table to match the requested timeline:

  • Purchase/order timeline -> dim_date.date
  • Delivery timeline -> dim_date_delivery.date

Data-anchored rolling ranges (e.g., "latest 12 months in the data"):

Do not emit native RelativeDate for data-anchored windows. Native RelativeDate always anchors to Now, not to the max available row in the model.

Instead:

  1. Ask Stage 1 to materialize a helper measure or flag such as Show Last 12 Purchase Months or Show Last 12 Delivery Months.
  2. Reuse the standard Advanced measure-filter pattern to require that helper field to evaluate to 1 for the visual.
  3. Keep the axis/filter date role aligned with the helper measure's anchor table (dim_date, dim_date_delivery, etc.).

This preserves deterministic historical-window behavior for sparse datasets.

Relative date ranges (legacy literal date substitution pattern):

Only use literal {{DateStart}} / {{DateEnd}} substitution when the request is for an explicit absolute range or when a downstream consumer cannot accept RelativeDate.

User Intent{{DateStart}}{{DateEnd}
Last 90 daysdatetime'<currentDate - 90 days>T00:00:00'datetime'<currentDate>T00:00:00'
Last 30 daysdatetime'<currentDate - 30 days>T00:00:00'datetime'<currentDate>T00:00:00'
Last 6 monthsdatetime'<currentDate - 6 months>T00:00:00'datetime'<currentDate>T00:00:00'
Last yeardatetime'<currentDate - 365 days>T00:00:00'datetime'<currentDate>T00:00:00'
Year-to-datedatetime'<Jan 1 of current year>T00:00:00'datetime'<currentDate>T00:00:00'

How to compute:

  1. Determine the current date at generation time (e.g., 2026-02-26).
  2. Subtract the specified duration to get the start date.
  3. Format both dates as datetime'YYYY-MM-DDT00:00:00' (Power BI datetime literal format).

Example — "last 90 days" generated on 2026-02-26:

  • {{DateStart}} = datetime'2025-11-28T00:00:00'
  • {{DateEnd}} = datetime'2026-02-26T00:00:00'

These computed values are substituted directly into the DateSpanLiteralValue fields in the absolute date-range filter. The {{DateRangeEntity}} and {{DateRangeProperty}} should reference the date table and date column from the semantic model (e.g., dim_date.date).


References

Preserving SemanticModel Content

Critical: After the SemanticModel is copied from the existing project (Stage 4), do NOT remove any tables, models, relationships, or definition files from the SemanticModel — even if they are not directly referenced by the current visual. The complete SemanticModel must remain intact because:

  • Other visuals or reports may depend on those tables/relationships
  • Removing tables breaks ref table declarations in model.tmdl
  • Removing relationships breaks foreign key integrity
  • Power BI Desktop validates the entire model on load, not just the visual's bindings

The visual generator should only add report files (visual.json, page.json, etc.) — never delete or modify existing SemanticModel content.

Error Handling

SituationResolution
Template placeholder not found in mappingUse empty string; log warning
Unknown visual typeFall back to tableEx template
Missing table/measure nameUse placeholder with TODO comment
Page dimensions exceed 1280x720Clip to default dimensions

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.