Skip to content

Architecture

This repo teaches agentic GitHub Copilot work with a Power BI Project. The semantic model is TMDL text. The report is PBIR JSON. The data is 100 percent synthetic retail pharmacy data for 12 stores, four regions, and dates from 2024-01-01 to 2025-12-31. No Fabric tenant is required.

Every agent customization is source controlled. Instructions, skills, custom agents, prompt files, hooks, MCP config, validation scripts, questions, expected answers, and lab checks are reviewable files.

  • Directory.github/
    • copilot-instructions.md
    • Directoryinstructions/
      • …
    • Directoryagents/
      • …
    • Directoryskills/
      • …
    • Directoryprompts/
      • …
    • Directoryhooks/
      • …
    • Directoryworkflows/
      • …
  • Directory.vscode/
    • mcp.json
  • Directoryconfig/
    • cloud-agent-mcp.template.json
  • Directoryfabric/
    • ContosoPharmacy.pbip
    • DirectoryContosoPharmacy.SemanticModel/definition/
      • …
    • DirectoryContosoPharmacy.Report/definition/
      • …
  • Directorydata/
    • Directorygenerated/
      • …
    • schema/tables.json
  • Directoryevaluation/
    • questions.yaml
    • Directoryexpected/
      • …
    • run.schema.json
    • rubric.md
  • Directoryrules/
    • …
  • Directoryscripts/
    • …
  • Directorytools/python/pharmacy_demo/
    • …
  • Directorylab/
    • …
  • Directorysite/
    • …
.github/copilot-instructions.md (lines 9-31)
## Repository map
| Path | What it holds |
|---|---|
| `fabric/ContosoPharmacy.pbip` | Power BI Project. Open it in Power BI Desktop (Windows only). |
| `fabric/ContosoPharmacy.SemanticModel/definition/` | The semantic model as TMDL. This is what most tasks change. |
| `fabric/ContosoPharmacy.Report/definition/` | The report as PBIR JSON. Field references must match model names. |
| `fabric/ai-prep/` | Drafts for Power BI's Prep data for AI (AI instructions, schema, synonyms, verified answers). |
| `data/generated/` | Synthetic CSVs. Generated; never edit by hand. |
| `evaluation/` | Business questions, run files, and the grading rubric. Expected answers are hidden from agents. |
| `rules/` | Naming conventions and required model objects, read by the checks and the skills. |
| `tools/python/pharmacy_demo/` | Python tools behind every script: data generation, lint, conventions, grading, lab checks, hooks. |
| `scripts/` | PowerShell 7 entry points. Learners run these. |
| `lab/` | Lab verification contract (`checks.yaml`) and checkpoint manifest (`checkpoints.json`). |
| `site/` | The self-paced lab site (Astro Starlight), published to GitHub Pages. |
| `.github/agents`, `skills`, `prompts`, `instructions`, `hooks` | The Copilot customizations the labs teach. |
## Commands
Windows PowerShell 7 is the primary shell. The same scripts run in `pwsh` on macOS and Linux.
| Task | Command |
|---|---|
Local authoring loop

Power BI Desktop is Windows-only. The Power BI Modeling MCP server can connect to the open Desktop model or inspect the PBIP folder. The source-controlled config pins the package version.

.vscode/mcp.json
{
// MCP servers for the Contoso Pharmacy lab. No secrets or tenant details belong in this file.
// Versions are pinned; update them deliberately and re-run Lab 04 after an update.
"servers": {
// Power BI Modeling MCP: connects to the model open in Power BI Desktop, or to the PBIP folder.
// If you installed the Power BI Modeling MCP VS Code extension instead, disable this entry so
// the tools don't appear twice.
"powerbi-modeling-mcp": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@microsoft/powerbi-modeling-mcp@0.5.0-beta.13", "--start"]
},
// Optional. Fabric MCP: Microsoft Fabric documentation and item definitions. Its docs tools
// work without a Fabric tenant. Start it from the MCP view only if you want it.
"fabric-mcp": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@microsoft/fabric-mcp@1.4.0", "server", "start", "--mode", "all"]
}
}
}
GitHub pull request and publishing loop

ci.yml has validate, site, and dependency-review jobs. validate runs on Ubuntu and Windows. site builds Astro Starlight. dependency-review runs only on pull requests.

.github/workflows/ci.yml (lines 1-88)
# Tenant-free CI. Nothing here needs Fabric, Power BI, or any secret.
name: CI
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
validate:
name: Validate (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
# The same bootstrap a learner runs. It installs the hashed requirements and runs every
# validation step (data, expected, questions, runs, tmdl, bindings, customizations, hygiene).
- name: Initialize and validate
run: ./scripts/Initialize-DevEnvironment.ps1
- name: pytest
run: |
$python = $IsWindows ? '.venv/Scripts/python.exe' : '.venv/bin/python'
& $python -m pytest tools/python/tests
if ($LASTEXITCODE) { exit $LASTEXITCODE }
- name: ruff
if: runner.os == 'Linux'
run: |
.venv/bin/python -m ruff check tools/python
if ($LASTEXITCODE) { exit $LASTEXITCODE }
.venv/bin/python -m ruff format --check tools/python
if ($LASTEXITCODE) { exit $LASTEXITCODE }
- name: Pester
run: |
$pester = Get-Module -ListAvailable Pester | Sort-Object Version -Descending | Select-Object -First 1
if (-not $pester -or $pester.Version -lt [version]'5.5.0') {
Install-Module Pester -MinimumVersion 5.5.0 -Scope CurrentUser -Force -SkipPublisherCheck
}
Import-Module Pester -MinimumVersion 5.5.0
$config = New-PesterConfiguration
$config.Run.Path = 'scripts/tests'
$config.Run.Exit = $true
$config.Output.Verbosity = 'Detailed'
Invoke-Pester -Configuration $config
- name: PSScriptAnalyzer
run: |
if (-not (Get-Module -ListAvailable PSScriptAnalyzer)) {
Install-Module PSScriptAnalyzer -Scope CurrentUser -Force
}
$findings = Invoke-ScriptAnalyzer -Path scripts -Recurse -Settings ./PSScriptAnalyzerSettings.psd1
$findings | Format-Table -AutoSize
if ($findings) { exit 1 }
# Warns instead of failing until the maintainer has pushed the checkpoint tags.
- name: Checkpoint tags
if: runner.os == 'Linux'
run: ./scripts/lab/Test-CheckpointTags.ps1 -AllowMissing
site:
name: Lab site build
runs-on: ubuntu-latest
timeout-minutes: 15

pages.yml publishes only when LAB_SITE_ENABLED is true, so template copies do not publish by accident.

.github/workflows/pages.yml (lines 1-65)
# Publishes the self-paced lab site to GitHub Pages.
# Runs only when the repository variable LAB_SITE_ENABLED is 'true', so copies made from the
# template don't publish until their owner turns Pages on (Settings > Pages > Source: GitHub Actions).
name: Lab site
on:
push:
branches: [main]
paths:
- "site/**"
- "lab/**"
- "evaluation/questions.yaml"
- "rules/**"
- ".github/agents/**"
- ".github/prompts/**"
- ".github/skills/**"
- ".github/instructions/**"
- ".github/workflows/pages.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
if: vars.LAB_SITE_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- id: pages
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
cache: npm
cache-dependency-path: site/package-lock.json
- name: Build
working-directory: site
env:
SITE_ORIGIN: ${{ steps.pages.outputs.origin }}
SITE_BASE: ${{ steps.pages.outputs.base_path }}
SITE_REPO: ${{ github.repository }}
run: |
npm ci --no-audit --no-fund
npm run check
npm run build
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: site/dist
deploy:
needs: build
runs-on: ubuntu-latest
permissions:
Data, model, and evaluation flow

The DuckDB oracle computes expected answers directly from CSV files. It does not trust the semantic model.

ModuleOwns
cli.pypython -m pharmacy_demo commands wrapped by PowerShell scripts.
datagen.pyDeterministic synthetic data and schema generation.
oracle.pyDuckDB expected answers.
evaluation.pyRun schema validation, grading, and comparison.
tmdl.pyTMDL parser and linter.
pbir.pyPBIR report binding checks.
conventions.pyRules C01 to C15 from rules/*.yaml.
repo_checks.pyHygiene, identifier, secret, PHI-like, and prose checks.
customizations.pyStatic checks for Copilot customization files.
hooks.pyHook decisions and hook logging.
labcheck/checks.pyLab progress checks from lab/checks.yaml.
modelgen/*Maintainer-only checkpoint model generator.
tools/python/pharmacy_demo/hooks.py (lines 1-24)
"""Agent hook handler shared by the pwsh and bash wrappers in scripts/hooks/.
Reads the hook payload (JSON) on stdin and writes a JSON decision on stdout. Accepts both the
Copilot CLI payload shape (toolName, toolArgs) and the snake_case shape (tool_name, tool_input).
Silence means "no opinion": this handler never returns "allow", so it can't bypass a permission prompt.
Output carries both shapes in one object: top-level fields for Copilot CLI and the cloud agent, and
hookSpecificOutput for the VS Code Local harness. Each runtime ignores the fields it doesn't use.
"""
from __future__ import annotations
import json
import os
import re
import sys
from datetime import UTC, datetime
from pathlib import Path
PATH_KEYS = {
"path",
"paths",
"file",
"files",
AreaVersion or pinSource
Node>=20 locally, Node 22 in workflowssite/package.json, workflows
Astro7.3.2site/package.json
Starlight0.42.1site/package.json
Mermaid11.17.2site/package.json
Python packagepharmacy-demo 1.0.0, Python >=3.12tools/python/pyproject.toml
Runtime depsduckdb, PyYAML, jsonschematools/python/pyproject.toml
Locked depsduckdb==1.5.5, jsonschema==4.26.0, PyYAML==6.0.3tools/python/requirements.txt
Dev depspytest==9.1.1, ruff==0.16.7tools/python/requirements-dev.txt
Power BI Modeling MCP@microsoft/powerbi-modeling-mcp@0.5.0-beta.13.vscode/mcp.json
Fabric MCP@microsoft/fabric-mcp@1.4.0.vscode/mcp.json
APMapm-cli 0.31.0apm.lock.yaml, cloud setup workflow
Skills for Fabricv0.3.17apm.yml, apm.lock.yaml
site/package.json (lines 1-28)
{
"name": "contoso-pharmacy-lab-site",
"private": true,
"type": "module",
"version": "1.0.0",
"description": "Self-paced lab site for the Contoso Pharmacy Copilot lab. Synthetic data only.",
"license": "MIT",
"engines": {
"node": ">=20"
},
"scripts": {
"sync": "node scripts/sync-reference.mjs",
"check": "node scripts/sync-reference.mjs && node scripts/check-content.mjs",
"dev": "node scripts/sync-reference.mjs && astro dev",
"build": "node scripts/sync-reference.mjs && astro build && node scripts/check-dist.mjs",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/markdown-remark": "7.3.1",
"@astrojs/starlight": "0.42.1",
"astro": "7.3.2",
"gray-matter": "4.0.3",
"mermaid": "11.17.2",
"sharp": "0.35.4",
"starlight-links-validator": "0.26.0",
"unist-util-visit": "5.1.0",
"yaml": "2.9.1"
tools/python/pyproject.toml
[project]
name = "pharmacy-demo"
version = "1.0.0"
description = "Synthetic Contoso Pharmacy data, oracle, validators, grader, and lab checks for the agentic Copilot lab."
readme = "README.md"
requires-python = ">=3.12"
license = { text = "MIT" }
dependencies = [
"duckdb>=1.1",
"PyYAML>=6.0",
"jsonschema>=4.23",
]
[project.optional-dependencies]
dev = ["pytest>=8.3", "ruff>=0.8"]
[project.scripts]
pharmacy-demo = "pharmacy_demo.cli:main"
[build-system]
requires = ["setuptools>=75"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find]
include = ["pharmacy_demo*"]
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-q"
[tool.ruff]
line-length = 110
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "W", "I", "B", "UP", "PTH"]
ignore = ["E501"]
apm.lock.yaml (lines 1-25)
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
- .agents/skills/semantic-model-authoring/references/semantic-model-ai-readiness.md