Skip to content

Lab 06: Skills and the read-only assessor

Time
About 35 minutes
Level
intermediate
Platforms
Windows 11, macOS (file mode, no Desktop)
Copilot surfaces
VS Code, Copilot CLI
You'll use
skill, custom-agent, prompt-file, handoff, apm, hook, mcp
Starts from
lab05-complete: Baseline model, plus a local-baseline question run copied from the committed sample.
Ends at
lab06-complete: Adds a sample assessment (lab/samples/assessment.md, copied to out/assessment.md).
Last verified
Not yet run end to end on Windows 11

Works on macOS

Nobody has run this lab end to end on Windows 11 yet. The steps follow the current docs for each tool, but menu labels may differ. The verify command at the end is the source of truth, and feedback is welcome.

Instructions are always on. Skills are loaded only when their description matches the task. That keeps the normal chat context smaller, while still giving agents deep guidance when they assess or change the model. In this lab the assessor loads the repo conventions, writes a structured assessment, and offers a handoff without making the fixes itself.

  • Skill: a folder with SKILL.md and optional references. Copilot reads the description first, then loads the body and references when needed. See Skills.
  • APM: Agent Package Manager. This repo uses it to install Microsoft’s Skills for Fabric semantic-model-authoring skill into .agents/skills/.
  • Read-only by instruction: the assessor has an edit tool so it can write out/assessment.md, but its agent instructions restrict edits to that file.
  • Deterministic convention check: scripts/Test-ModelConventions.ps1 reads rules/*.yaml and reports rule-coded findings. The agent explains and prioritizes those findings.
  • Handoff: the assessor shows Fix these findings, but you do not click it until Lab 07.
  • You finished Lab 05 and evaluation/runs/local-baseline.json exists.
  • You have VS Code open at the repo root.
  • Power BI Desktop is optional here. The assessor can read TMDL files directly.
  • If you skipped Lab 05, restore the checkpoint below.

Open the skill and the data dictionary:

.github/skills/pharmacy-model-conventions/SKILL.md (lines 1-61)
---
name: pharmacy-model-conventions
description: House conventions for the Contoso Pharmacy semantic model, covering business names, descriptions, hidden keys, summarization, display folders, required relationships and measures, and the AI-readiness checklist. Use when you assess, review, or change model names, descriptions, measures, relationships, or the date table, or when you need the standard business name for a source column.
---
# Contoso Pharmacy model conventions
These conventions make the model easy for people and for AI tools (Power BI Copilot, Fabric data agents, and coding agents) to read. They are checked by rules C01 to C15.
The rules themselves are data, not prose. Read them from:
- `rules/naming-conventions.yaml`: name pattern, forbidden abbreviations, description length, hidden-column patterns, additive columns, raw-code columns, display folders.
- `rules/required-model-objects.yaml`: required tables, relationships, date table settings, the twelve required measures by display folder, default labels, and the AI prep requirements.
## Get the current findings
Run the checker instead of reviewing by eye. It's deterministic and it's the same code the lab checks use.
```powershell
pwsh ./scripts/Test-ModelConventions.ps1 # table of findings
pwsh ./scripts/Test-ModelConventions.ps1 -Json # for agents
pwsh ./scripts/Test-ModelConventions.ps1 -Rule C07,C08
```
On macOS or Linux, `pwsh` runs the same scripts. Every finding has a rule, severity, object, finding, and fix.
## Rules
| Rule | Title | What good looks like |
|---|---|---|
| C01 | Business-friendly names | Title Case words with spaces. No underscores or abbreviations like `rx`, `dt`, `nm`, `qty`. Use the names in [the data dictionary](references/data-dictionary.md). |
| C02 | Descriptions present | Every table, visible column, and measure has a `///` description. |
| C03 | Description length | 20 to 200 characters. Say what it is, its grain or unit, and when to use it. |
| C04 | Keys and helpers hidden | Surrogate keys, foreign keys, `Fill ID`, and sort-by number columns are hidden. |
| C05 | Default summarization | `summarizeBy: none` everywhere except `Quantity` and `Days Supply` (sum) and `Processing Time (min)` (average). |
| C06 | Raw codes decoded | `Fill Type Code` (N or R) is hidden, and a visible `Fill Type` column shows New or Refill. |
| C07 | Required relationships | Prescription Fill to Date, Store, and Medication, many-to-one. |
| C08 | Single-direction filters | No bidirectional relationships. |
| C09 | Marked date table | `Date` has `dataCategory: Time`, and its `Date` column has `isKey`. |
| C10 | Auto date/time off | No `LocalDateTable` tables; `__PBI_TimeIntelligenceEnabled = 0`. |
| C11 | Sort-by columns | Month, Year Month, and Day of Week sort by their number columns. |
| C12 | Required measures | The twelve measures in `rules/required-model-objects.yaml`, with those exact names. |
| C13 | Measure format strings and folders | Every measure has `formatString` and a `displayFolder` from Volume, Refills, Processing, or Time Comparison. |
| C14 | Default labels | `Store Name` and `Medication Name` are the default labels of their tables. |
| C15 | Q&A and AI prep drafts | Q&A is enabled and `fabric/ai-prep/` has the five draft files. See the `prep-data-for-ai` skill. |
## Writing descriptions
A good description answers "what is this, and when do I use it?" in one or two short sentences.
- Tables: the grain and what it's for. "One row per prescription fill (new or refill) at a store."
- Columns: meaning, values or unit, and anything surprising. "Minutes from intake to ready for pickup."
- Measures: what it counts or computes, and the business words people use for it. Mention how it differs from similar measures.
- Don't restate the name ("The store name column"). Don't mention patients; this data has none.
- Stay under 200 characters. Power BI Copilot reads only the first 200.
More examples are in [references/examples.md](references/examples.md). The full AI-readiness checklist is in [references/ai-readiness-checklist.md](references/ai-readiness-checklist.md).
## Measures
- One measure, one idea. Build on base measures: `[Refills]` is `CALCULATE([Prescriptions Filled], ...)`, not a second `COUNTROWS`.
- Use `DIVIDE()` for ratios so empty periods return blank instead of an error.
.github/skills/pharmacy-model-conventions/references/data-dictionary.md (lines 1-21)
# Data dictionary
Standard business names for every source column. Rename the model object to the business name and keep `sourceColumn` set to the source name.
## Tables
| Source table | Business name | Grain |
|---|---|---|
| `dim_date` | Date | One row per calendar day, 2024-01-01 to 2025-12-31 |
| `dim_store` | Store | One row per store (12 stores, 4 regions) |
| `dim_med` | Medication | One row per medication product |
| `fact_rx_fill` | Prescription Fill | One row per prescription fill, new or refill |
## Date
| Source | Business name | Notes |
|---|---|---|
| `cal_dt` | Date | Key of the marked date table |
| `yr` | Year | Don't summarize |
| `qtr` | Quarter | Q1 to Q4 |
| `yr_qtr` | Year Quarter | For example 2025-Q4 |

The skill description is what gets it loaded. The references provide the details only after the skill is active.

You should see: rules C01 to C15, the command for the deterministic checker, and the standard business names: Date, Store, Medication, and Prescription Fill.

Step 2. Install the Skills for Fabric semantic-model-authoring skill

Section titled “Step 2. Install the Skills for Fabric semantic-model-authoring skill”
Optional

The lab check for this step is optional, but installing the skill shows how a third-party package reaches Copilot without copying files into .github/skills/.

Terminal window
python -m pip install --user apm-cli==0.31.0
apm --version
apm install --only apm --target copilot

If the installer asks you to confirm the package plan, read it first. It should deploy only the APM skill dependency from this repo’s apm.yml, then answer y.

The repo manifest pins the APM install:

apm.yml (lines 1-26)
# APM (Agent Package Manager) manifest for the Contoso Pharmacy lab.
#
# Consume: installs the third-party Skills for Fabric skill this lab uses, pinned to a release.
# apm install --only apm --target copilot
# --only apm skips the MCP servers that Skills for Fabric declares; this repo configures its own
# in .vscode/mcp.json. See Lab 06.
#
# Publish: this lab's skills live in .github/skills (the folder the lab teaches), which APM treats
# as build output, so this manifest does not export them as a package. Other repos install one
# skill at a time by subdirectory instead, for example:
# apm install sabajamalian/fabric-powerbi-ai-devops-demo/.github/skills/pharmacy-model-conventions
# See Lab 14.
name: fabric-powerbi-ai-devops-demo
version: 1.0.0
description: Copilot skills, agents, and prompts for making a Power BI semantic model AI-ready, from the Contoso Pharmacy lab. Synthetic data only.
license: MIT
repository: https://github.com/sabajamalian/fabric-powerbi-ai-devops-demo
keywords: [power-bi, tmdl, semantic-model, copilot, skills]
targets:
- copilot
dependencies:
apm:
- git: https://github.com/microsoft/skills-for-fabric.git
ref: v0.3.17
apm.lock.yaml (lines 1-24)
lockfile_version: '1'
apm_version: 0.31.0
dependencies:
- repo_url: microsoft/skills-for-fabric
name: skills-for-fabric
host: github.com
resolved_commit: 65bfb5eb2c488cd03183df88f76ac3de65dcb910
resolved_ref: v0.3.17
version: 0.3.17
package_type: skill_bundle
deployed_files:
- .agents/skills/semantic-model-authoring
- .agents/skills/semantic-model-authoring/SKILL.md
- .agents/skills/semantic-model-authoring/apm.yml
- .agents/skills/semantic-model-authoring/references/connection-binding.md
- .agents/skills/semantic-model-authoring/references/dax-guidelines.md
- .agents/skills/semantic-model-authoring/references/dax-perf-decision-guide.md
- .agents/skills/semantic-model-authoring/references/dax-perf-patterns.md
- .agents/skills/semantic-model-authoring/references/direct-lake-guidelines.md
- .agents/skills/semantic-model-authoring/references/field-parameters.md
- .agents/skills/semantic-model-authoring/references/metadata-discovery.md
- .agents/skills/semantic-model-authoring/references/modeling-guidelines.md
- .agents/skills/semantic-model-authoring/references/naming-conventions.md
- .agents/skills/semantic-model-authoring/references/pbip.md

Then run the optional check:

Terminal window
pwsh ./scripts/lab/Test-LabProgress.ps1 -Lab 06 -Only L06-fabric-skill

You should see: either PASS L06-fabric-skill with a path such as .agents/skills/semantic-model-authoring/SKILL.md, or a warning if you skipped this optional install. apm install --only apm --target copilot should not overwrite .vscode/mcp.json.

  1. Open Copilot Chat: Ctrl+Alt+I (macOS: Ctrl+Cmd+I), or View > Chat.
  2. Switch to Agent mode.
  3. Pick model-assessor from the agent dropdown.
  4. Type /assess-model and press Enter. If VS Code asks for the optional focus, leave it blank.
  5. Approve terminal commands after you read them.

The prompt is short because the agent file carries the method and output contract:

.github/prompts/assess-model.prompt.md
---
name: assess-model
description: Assess the semantic model against the team conventions and AI-readiness checklist and write out/assessment.md.
agent: model-assessor
argument-hint: Optional focus area
---
Assess the Contoso Pharmacy semantic model in `fabric/ContosoPharmacy.SemanticModel/`.
${input:focus:Optional focus, or leave blank for the whole model}
1. Run `pwsh ./scripts/Test-ModelConventions.ps1 -Markdown` for the deterministic findings.
2. Inspect the model through Power BI Modeling MCP if it's connected, otherwise read the TMDL files.
3. Write `out/assessment.md` in the format your instructions define, with at least 8 findings.
4. In chat, list the three fixes that would most improve AI answers to the business questions.
.github/agents/model-assessor.agent.md (lines 22-53)
## Method
1. Load the `pharmacy-model-conventions` skill. It defines rules C01 to C15 and the AI-readiness checklist.
2. Run the deterministic check for the baseline list of findings:
```powershell
pwsh ./scripts/Test-ModelConventions.ps1 -Markdown
```
3. Look at the model yourself. If Power BI Modeling MCP is connected, use it to list tables, columns, measures, and relationships; otherwise read the `.tmdl` files. Look for problems the script can't judge:
- Names that are technically valid but unclear to a business user.
- Columns an AI tool would misuse, for example a minutes column it might sum.
- Business questions the model can't answer without an explicit measure (month-over-month change, refill index against the chain).
- Terms that need a definition in AI instructions ("latest month", "previous period", "unusually high").
4. Group related findings. Forty "no description" rows are one finding per table, not forty.
## Output: out/assessment.md
```markdown
# Model assessment: <date>
<Two or three sentences: overall state and the three changes that matter most.>
| # | Rule | Severity | Object | Finding | Fix |
|---|---|---|---|---|---|
| 1 | C01 | high | table fact_rx_fill | Raw source name; users and AI won't recognize it. | Rename to Prescription Fill. |
```
- Keep the columns exactly: `#`, `Rule`, `Severity`, `Object`, `Finding`, `Fix`. The lab check reads them.
- Severity is `high`, `medium`, or `low`. Sort high first.
- Use `AI` in the Rule column for a finding that isn't one of C01 to C15.
- Aim for 10 to 25 rows.
End your chat reply with the top three fixes and suggest the **Fix these findings** handoff.

You should see: the assessor runs pwsh ./scripts/Test-ModelConventions.ps1 -Markdown, reads the model through MCP or TMDL files, and writes out/assessment.md.

Open out/assessment.md in VS Code. The lab check looks for a Markdown table with at least eight findings and these columns: Rule, Severity, Object, Finding, and Fix.

Run the check:

Terminal window
pwsh ./scripts/lab/Test-LabProgress.ps1 -Lab 06 -Only L06-assessment

The deterministic rule set behind the assessment lives here:

rules/required-model-objects.yaml (lines 1-39)
# Objects the AI-ready model must contain. Read by conventions.py and the lab checks.
version: 1
tables: [Date, Store, Medication, Prescription Fill]
date_table:
table: Date
key_column: Date
data_category: Time
auto_date_time: false
sort_by:
Month: Month Number
Year Month: Year Month Number
Day of Week: Day of Week Number
hierarchy:
name: Calendar
levels: [Year, Year Quarter, Year Month, Date]
relationships:
- from: ["Prescription Fill", "Fill Date"]
to: ["Date", "Date"]
- from: ["Prescription Fill", "Store Key"]
to: ["Store", "Store Key"]
- from: ["Prescription Fill", "Medication Key"]
to: ["Medication", "Medication Key"]
single_direction_only: true
measures:
table: Prescription Fill
required:
Volume: [Prescriptions Filled, New Prescriptions]
Refills: [Refills, Refill Rate, Refill Rate Index vs Chain, High Refill Activity Flag]
Processing: [Avg Processing Time (min)]
Time Comparison: [Prescriptions Filled PM, MoM Change, MoM %, Prescriptions Filled PY, YoY %]
default_labels:
Store: Store Name
Medication: Medication Name
scripts/Test-ModelConventions.ps1 (lines 1-35)
#Requires -Version 7.2
<#
.SYNOPSIS
Lists where the semantic model breaks the team conventions in rules/*.yaml.
.DESCRIPTION
Rules C01 to C15 cover names, descriptions, hidden keys, summarization, relationships, the date
table, measures, display folders, and AI metadata flags. The baseline model breaks most of them
on purpose. This is the deterministic check behind the model-assessor agent and Labs 07 to 09.
By default it reports findings and exits 0; use -Strict to exit 1 when there are findings.
.PARAMETER Rule
Only report these rule IDs, comma-separated, for example C01,C02.
.PARAMETER Json
Print findings as JSON.
.PARAMETER Markdown
Print findings as a Markdown table (Rule, Severity, Object, Finding, Fix).
.PARAMETER OutFile
Also write the Markdown table to this file.
.PARAMETER Strict
Exit 1 when there are findings.
.EXAMPLE
pwsh ./scripts/Test-ModelConventions.ps1 -Rule C01
#>
[CmdletBinding(PositionalBinding = $false)]
param(
[string[]] $Rule,
[switch] $Json,
[switch] $Markdown,
[string] $OutFile,
[switch] $Strict
)
. (Join-Path $PSScriptRoot '_Common.ps1')
$Rule = @($Rule | ForEach-Object { $_ -split ',' } | ForEach-Object { $_.Trim().ToUpperInvariant() } | Where-Object { $_ })
$bad = @($Rule | Where-Object { $_ -notmatch '^C\d{2}$' })

You should see: PASS L06-assessment and at least eight findings in out/assessment.md. The highest-severity items should include raw names, missing descriptions, relationship/date-table problems, and missing measures.

Step 5. Ask the assessor to fix one finding

Section titled “Step 5. Ask the assessor to fix one finding”

In the same model-assessor chat, type:

Fix the highest-severity finding now.

The correct behavior is refusal plus a handoff. The assessor has edit, but its instructions say the only file it creates or edits is out/assessment.md:

.github/agents/model-assessor.agent.md (lines 1-20)
---
name: model-assessor
description: Reviews the Contoso Pharmacy semantic model against the team conventions and AI-readiness checklist, without changing it, and writes prioritized findings to out/assessment.md. Use before improving the model or to explain why AI answers are weak.
argument-hint: Optional focus, for example "names only" or "measures and time intelligence"
tools: ['read', 'search', 'execute', 'edit', 'todo', 'powerbi-modeling-mcp/*']
handoffs:
- label: Fix these findings
agent: model-improver
prompt: Fix the findings in out/assessment.md, highest severity first. Start with names and descriptions, and rebind the report after any rename.
send: false
---
# Model assessor
You review the semantic model in `fabric/ContosoPharmacy.SemanticModel/` and report what makes it hard for people and AI tools to use. You don't fix anything.
## Boundaries
- The only file you create or edit is `out/assessment.md`. Never edit TMDL, PBIR, data, or evaluation files.
- Don't read `evaluation/expected/` or `evaluation/questions.yaml`.
- All data is synthetic. Don't speculate about real pharmacies or patients.

This is an instruction boundary, not a hard tool permission. Lab 11 adds hooks that make some rules deterministic by denying forbidden edits and logging post-edit lint.

You should see: the assessor explains that it cannot edit TMDL or PBIR, summarizes what should be fixed, and points you to Fix these findings instead of changing the model.

Step 6. Leave the handoff visible for Lab 07

Section titled “Step 6. Leave the handoff visible for Lab 07”

Do not click Fix these findings yet. You need the button visible because Lab 07 starts by clicking it.

If you do not see the button:

  1. Keep the model-assessor chat open.
  2. Make sure the latest assessor response finished.
  3. If needed, open chat history from the clock icon at the top of the Chat view and select this conversation later.

You should see: a completed model-assessor answer with the Fix these findings handoff button under it, plus out/assessment.md in the repo.

Run this from the repo root in a PowerShell 7 terminal (Terminal > New Terminal in VS Code):

Terminal window
pwsh ./scripts/lab/Test-LabProgress.ps1 -Lab 06

When everything is done you should see:

Expected output
PASS L06-assessment out/assessment.md has at least 8 findings with rule, severity, object, finding, and fix
PASS L06-fabric-skill Skills for Fabric semantic-model-authoring skill is installed
Lab 06: 2 of 2 checks passed.

Each FAIL line prints a hint and a link back to the step on this page that fixes it. The command exits with code 0 when every required check passes.

CheckWhat it looks atIf it fails
L06-assessmentout/assessment.md has at least 8 findings with rule, severity, object, finding, and fixRun /assess-model with the model-assessor agent. It writes out/assessment.md. Go to step 4
L06-fabric-skill (optional)Skills for Fabric semantic-model-authoring skill is installedRun apm install (see the lab) or /plugin install powerbi-authoring@fabric-collection in Copilot CLI. Go to step 2

Optional checks can FAIL without failing the lab.

apm is not recognized after pip install

pip install --user puts apm in your Python user Scripts folder, which often isn’t on PATH (pip prints a warning naming the folder). Add it for this terminal session, then run apm --version again:

Terminal window
$scripts = python -c "import sysconfig; print(sysconfig.get_path('scripts', sysconfig.get_preferred_scheme('user')))"
$env:PATH = $scripts + [IO.Path]::PathSeparator + $env:PATH
apm --version

To make it permanent on Windows, add that folder under Settings > System > About > Advanced system settings > Environment Variables > Path for your user, then open a new terminal.

The semantic-model-authoring skill check fails

This check is optional. If you want it to pass, rerun apm install --only apm --target copilot from the repo root and check for .agents/skills/semantic-model-authoring/SKILL.md. If APM installed globally, the check also looks under your home directory.

APM changed .vscode/mcp.json

You likely missed --only apm. Restore the workspace MCP config with git restore .vscode/mcp.json, then rerun apm install --only apm --target copilot. This repo manages MCP servers itself.

The assessor writes fewer than eight findings

Ask it to rerun pwsh ./scripts/Test-ModelConventions.ps1 -Markdown and rewrite out/assessment.md with one row per grouped finding. The check reads the table shape and count, not the chat answer.

The skill does not appear to load

Ask the agent, “Which skills did you use?” Then expand the chat references. If pharmacy-model-conventions is missing, restart the chat and ask specifically to assess names, descriptions, relationships, date table, and measures against the Contoso Pharmacy conventions.

The assessor starts editing TMDL

Reject the edit. Remind it: “You are model-assessor. Your only editable file is out/assessment.md.” The restriction is in the agent instructions. Hook-enforced edit denials are covered in Lab 11.

Why does the assessor load a skill instead of relying only on repo instructions?

Show the answer

Skills provide task-specific guidance and references only when the task needs them.

Repo instructions are always on. Skills load on demand and can bring deeper reference material into context for a specific task.

Why can model-assessor write out/assessment.md but not fix the model?

Show the answer

Its instructions restrict edits to out/assessment.md, even though the tool list includes edit.

The boundary is in the agent instructions. Hooks can enforce some rules, but this lab demonstrates the instruction boundary first.

What does the L06-assessment check require?

Show the answer

At least eight findings in out/assessment.md with rule, severity, object, finding, and fix fields.

The check reads out/assessment.md and validates the findings table shape and count. It does not expect model changes yet.

You read a repo skill, installed the third-party Skills for Fabric authoring skill, ran a read-only assessor, and produced out/assessment.md. You also saw the assessor offer a handoff without taking edit ownership of the model. Next you click that handoff and let the improver handle names, descriptions, and report bindings.

Next: Lab 07: Handoffs, names, and descriptions

  • Run pwsh ./scripts/Test-ModelConventions.ps1 -Json and compare the machine-readable findings with the Markdown assessment.
  • Ask the assessor to focus only on measures, then compare the shorter assessment with the full one.
  • Read .github/skills/pharmacy-model-conventions/references/ai-readiness-checklist.md and mark which findings affect AI answers most.