Pedro Mariano / portfolio

Pricing health dashboard

One question for the owner of a small store: where am I losing margin because my prices fell behind my costs? The answer is a ranked to-do list for the next price review.

6tables in a star schema, with one-way relationships
10DAX measures, each on a stated basis
3report pages: overview, below cost, price changes
90 dayswindow for money lost selling below cost
Pricing health dashboard: revenue, gross margin, stale costs, money lost below cost, markup by family and the below-cost list

The overview and the below-cost list, on the demo's synthetic store (a year of sales for 128 products).

What it shows

Four numbers, then the list.

  • Revenue and gross margin %, on revenue without VAT.
  • Revenue on costs older than two years: how much of the business is priced on costs nobody has checked.
  • Money lost selling below cost in the last 90 days.
  • Markup by product family against the family's usual markup, in red where it is below.

The table under it is the point of the report: products sold below cost, ranked by money lost. In the real store, the same idea uses audit materiality (ISA 320) to decide what is worth a person's time.

Data model

A star schema, kept boring on purpose.

TableOne row perKey
fact_salesproduct and dayproduct_code, day
fact_price_changesprice written or undoneproduct_code
dim_productproductcode
dim_familyproduct family, with its usual marginfamily
below_cost_90dproduct sold below cost in 90 dayscode
Calendarday (built in DAX)Date

Measures

Revenue ex VAT = SUMX ( fact_sales, fact_sales[revenue] / ( 1 + RELATED ( dim_product[vat] ) / 100 ) )
Cost of goods  = SUMX ( fact_sales, fact_sales[qty] * RELATED ( dim_product[cost] ) )
Gross margin % = DIVIDE ( [Revenue ex VAT] - [Cost of goods], [Revenue ex VAT] )
Markup %       = DIVIDE ( [Revenue ex VAT], [Cost of goods] ) - 1
-- same basis as dim_family[usual_margin]: a markup over cost, not a margin on sales
Stale cost revenue % =
    DIVIDE ( CALCULATE ( [Revenue], dim_product[cost_date] < DATE ( 2024, 9, 30 ) ), [Revenue] )

A detail that matters

Store owners say "margin" for two different things. The family targets are a markup over cost, so they are compared with Markup %, never with Gross margin %. Mixing the two makes every family look worse than it is.

How it is built

The data comes from the demo's own report command, which writes the CSV files the model loads. The image above is drawn by a small Python script that computes the same measures with pandas, so the numbers can be checked without Power BI. The README has the model, the DAX and the page layout to rebuild it in Power BI Desktop.

  • Power BI
  • DAX
  • Power Query
  • Star schema
  • pandas
  • matplotlib