Procurement data · Power BI
Build a government contract dashboard with measures you can explain.
Download a Power BI project for a pinned public procurement sample. Build buyer and category views with separate notice-stage counts, coverage measures and refresh checks.
A useful government contract dashboard in Power BI starts with a declared row meaning, an edition date and measures that respect both. This walkthrough uses a public procurement sample to show buyer activity, notice stages, category distribution and field coverage. It deliberately keeps notice counts separate from award counts and spending.
The download contains an editable Power BI project, its source sample, Power Query code and DAX measures. Validation as of September 10, 2026: the data reconciliation and 14 PBIR JSON schema checks pass. Opening the project, executing M/DAX, rendering visuals and refreshing inside Power BI Desktop have not been tested here. The package is a PBIP starter project, not a Desktop-exported PBIT file or a verified dashboard screenshot.
Start with a public market question
This report answers: which buyers and notice stages appear in one declared public procurement sample? It supports evaluating the data before investing in a recurring reporting workflow.
It does not measure your organization's purchase orders, savings or supplier performance. It also does not identify every open bid or calculate a complete addressable market. Those questions require different inputs and acceptance criteria.
Use the government contract analysis guide for analytical methodology. This page focuses on implementing a small report in Power BI and checking the numbers it displays.
Know what the sample contains
The input is the same 217-row public sample used in the tender API comparison. It selects records with at least one CPV code beginning 72 from WebTruffle's September 1, 2026 edition, generated on September 2 at 13:25:55 UTC. The download retains the source CSV and receipt, including the tagged release, input hashes and source licence references.
This is a pinned teaching sample, not a September 10 update. Its observed distribution is:
| Source | Notice-version rows | |---|---:| | TED | 197 | | Contracts Finder | 13 | | Find a Tender | 7 | | Total | 217 |
The row key combines source, source_id and source_release_id. All 217 keys are unique in this sample. That does not prove the underlying procurements are unique across publication sources. No cross-source entity resolution or deduplication is claimed.
The prepared notices.csv preserves one row per source notice version. Buyer names remain display labels. Source links and titles remain available for inspection. Raw dates, codes and values remain in source.csv; the reporting table keeps only the fields needed for this exercise.
Load the Power BI project
Extract the complete ZIP, preserving the Procurement.Report and Procurement.SemanticModel folders beside Procurement.pbip. Use a Power BI Desktop version that supports Power BI projects and the enhanced report format; enable the relevant preview option if your installed version requires it. Microsoft documents PBIP as text-based report and model definitions. (Power BI projects)
Open Procurement.pbip, then set the SampleFolder text parameter under Transform data → Manage parameters to the extracted folder containing notices.csv. Use the folder path without a trailing backslash. The supplied default, C:\WebTrufflePowerBI, is a placeholder.
Choose Close & Apply, then Refresh. The project uses an import-mode model; it contains definitions rather than a preloaded data cache. The supplied model uses model.bim, which Microsoft supports for TMSL-based project models. (Semantic model project files)
The Power Query source uses explicit UTF-8 and CSV quoting:
Csv.Document(
File.Contents(SampleFolder & "\notices.csv"),
[Delimiter=",", Encoding=65001, QuoteStyle=QuoteStyle.Csv]
)
The full query checks the header set and missing or duplicate keys. It keeps identifiers as text and converts only the two presence flags to whole numbers. This avoids automatic type inference changing a code or interpreting a source timestamp prematurely. Microsoft's Text/CSV connector guidance explains these import and inference settings.
If your Desktop version cannot open the project, the bundle also includes Notices.pq and Measures.dax. Import the CSV manually, create the parameter and a query named Notices, then add the measures individually. Record any project-loading error before treating the package as accepted.
Separate notice counts from awards and money
The model starts with a row-count measure:
Notice versions = COUNTROWS(Notices)
Its award-stage measure is deliberately named for what it counts:
Award-stage notice versions =
CALCULATE(
[Notice versions],
KEEPFILTERS(Notices[stage] = "award")
)
The corresponding opportunity-stage measure uses stage = "opportunity". KEEPFILTERS makes the stage restriction intersect with the current selection rather than replacing it. With no filters, the expected stage counts are:
| Normalized stage | Notice-version rows | |---|---:| | Award | 109 | | Opportunity | 93 | | Contract | 7 | | Other | 6 | | Planning | 2 |
Do not relabel the 109 award-stage notice versions as “109 contracts won.” One notice can describe several awards, and versions or publication sources can repeat information. Likewise, opportunity-stage rows are not automatically open or eligible opportunities today.
The model includes no amount column and no spending total. The source's estimated_value field is not enough to establish a comparable monetary basis across all stages and sources. If you need financial reporting, use an award-grain table with declared measures and currencies, such as the workflow in the government contract awards Python guide. Keep separate facts at their declared grains; Microsoft's star-schema guidance explains why this matters when the model grows.
Build buyer and category views without duplicating rows
The project defines six summary cards and four supporting visuals: a buyer table, category bar chart, stage table and source table. The buyer table includes notice versions, award-stage notice versions and deadline coverage. The sample contains 191 distinct buyer-name labels; that is not a verified count of legal entities.
The category view uses the first supplied CPV code's two-digit division. It does not claim that this is the official main classification. Each notice receives exactly one display bucket, so the chart does not multiply rows by expanding every code.
That choice produces a useful check: 162 rows have first-listed division 72, while 55 have another first-listed division. Every row still qualified through at least one 72 code. Filtering on any matching code and grouping by the first listed code answer different questions.
If you need an all-codes view, create a separate notice-to-code relationship table and count distinct notice keys under a category selection. Category subtotals can then overlap and need not add to the overall notice count. Do not join that expanded table back into an amount-bearing fact and sum repeated values.
Show missing data beside the counts
The prepared table has deadline_present and value_present flags. A flag is one when the source field contains nonempty text, otherwise zero. A supplied zero value counts as present.
Deadline coverage =
DIVIDE(SUM(Notices[deadline_present]), [Notice versions])
The unfiltered expected values are 35 of 217 rows with a deadline (16.1%) and 111 of 217 with a value (51.2%). These denominators include every stage. They are sample coverage figures, not universal data-quality grades: a response deadline might not be applicable to an award notice.
A populated deadline is not necessarily parseable, current or still open. A populated monetary field does not establish currency comparability or permission to aggregate it. Put these definitions beside the report when you share it, and inspect stage-filtered coverage before diagnosing a source problem.
Refresh the data without changing its meaning
Desktop refresh rereads notices.csv; it does not collect new government records. Refreshing the pinned file today should leave the edition at 2026-09-01. The edition card reflects the data column, while the page title is static and needs updating when you adapt the report.
For a recurring workflow, first prepare and validate a new table upstream. Check source coverage, schema, keys and field meaning, then replace the reporting table and edition metadata together. Refresh and reconcile the result. Do not append daily snapshots blindly: repeated notice observations would inflate these counts. Historical analysis needs a model designed for versions over time.
After publishing a Desktop report to the Power BI service, configure the semantic model's credentials and refresh connection. A local-file source needs an appropriate gateway for scheduled refresh. Moving the table to a supported cloud source requires a deliberate connector change and a refresh test; a local PBIP file alone does not configure that service. (Power BI refresh guidance)
Check the report before sharing it
With all selections cleared, compare the report to expected-results.json: 217 notice versions, 109 award-stage, 93 opportunity-stage, 16.1% deadline coverage and 51.2% value coverage. Confirm the edition card too.
Select a buyer or category and verify that the linked visuals respond. Clear the selection and confirm the totals return. Inspect a source row through its retained link, then rerun refresh against the same file and check that counts remain unchanged.
For reproducibility, run python -B build_example.py in the extracted folder before customizing it. The script checks the source SHA-256, rebuilds the prepared table and regenerates the project. It overwrites generated project files, so run it on a fresh copy if you have edited the report. Python checks and JSON schema validation do not execute M or DAX; Desktop validation remains a separate step.
After the project loads and reconciles in Desktop, you can save a PBIX or export a PBIT through File → Export → Power BI template. The downloadable PBIP should not be renamed to .pbit; that format is a Desktop export. (Microsoft template instructions)
Request tables that fit your report
Try the project and inspect the free public procurement tables. Free access already includes public CSVs. The paid work is agreeing and maintaining the sources, schema, validation and delivery your reporting process needs.
This tutorial is an evaluation example. It does not imply that WebTruffle sells Power BI dashboard implementation or operates your tenant. A useful feed request names the required sources, row meaning, fields, destination, schedule and acceptance checks.
Frequently asked questions
Is the download a PBIT template?
It is an editable PBIP starter project with a sample and source code. It has passed data and report-schema checks, but has not been opened in Power BI Desktop here. After validating it in Desktop, you can export a PBIT.
Can I sum the public contract values?
This sample does not establish a consistent financial basis, so the report has no amount measure. Start with a separate award-level fact table, verify currency and value semantics, and prevent child relationships from repeating amounts.
Does Refresh fetch today's opportunities?
No. The supplied query rereads a local pinned sample. Recurring collection, table validation and service refresh configuration must be established separately.