Architecture
Big picture
Section titled “Big picture”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.
Repository layout
Section titled “Repository layout”Directory
.github/copilot-instructions.mdDirectory
instructions/- …
Directory
agents/- …
Directory
skills/- …
Directory
prompts/- …
Directory
hooks/- …
Directory
workflows/- …
Directory
.vscode/mcp.json
Directory
config/cloud-agent-mcp.template.json
Directory
fabric/ContosoPharmacy.pbipDirectory
ContosoPharmacy.SemanticModel/definition/- …
Directory
ContosoPharmacy.Report/definition/- …
Directory
data/Directory
generated/- …
schema/tables.json
Directory
evaluation/questions.yamlDirectory
expected/- …
run.schema.jsonrubric.md
Directory
rules/- …
Directory
scripts/- …
Directory
tools/python/pharmacy_demo/- …
Directory
lab/- …
Directory
site/- …
## 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
Section titled “Local authoring loop”flowchart LR VS[VS Code] --> Chat[Copilot agent mode] Chat --> Instructions[Repo and path instructions] Chat --> Agents[Custom agents and skills] Chat --> MCP[Power BI Modeling MCP] MCP --> Desktop[Power BI Desktop on Windows] Desktop --> PBIP[PBIP project] Chat --> Files[TMDL and PBIR files] Files --> Scripts[Validation scripts] Scripts --> Hooks[Hook log and lint feedback] Hooks --> Chat
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.
{ // 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 loop
Section titled “GitHub loop”flowchart LR Branch[Feature branch] --> PR[Pull request] PR --> CI[CI validate and site jobs] PR --> Review[Copilot code review] Issue[Scoped issue] --> Cloud[Copilot cloud agent] Cloud --> PR CI --> Merge[Merge to main] Merge --> Pages[GitHub Pages] Pages --> Site[Self-paced lab site]
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.
# 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: 15pages.yml publishes only when LAB_SITE_ENABLED is true, so template copies do not publish by accident.
# 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 and evaluation flow
Section titled “Data and evaluation flow”flowchart LR Gen[pharmacy_demo.datagen] --> CSV[data/generated CSVs] Gen --> Schema[data/schema/tables.json] CSV --> TMDL[Semantic model TMDL] TMDL --> PBIR[PBIR report] CSV --> Oracle[DuckDB oracle] Oracle --> Expected[evaluation/expected Q01 to Q05] TMDL --> Tester[question-tester agent] Tester --> Runs[evaluation/runs] Expected --> Grader[grader] Runs --> Grader
The DuckDB oracle computes expected answers directly from CSV files. It does not trust the semantic model.
Python package organization
Section titled “Python package organization”| Module | Owns |
|---|---|
cli.py | python -m pharmacy_demo commands wrapped by PowerShell scripts. |
datagen.py | Deterministic synthetic data and schema generation. |
oracle.py | DuckDB expected answers. |
evaluation.py | Run schema validation, grading, and comparison. |
tmdl.py | TMDL parser and linter. |
pbir.py | PBIR report binding checks. |
conventions.py | Rules C01 to C15 from rules/*.yaml. |
repo_checks.py | Hygiene, identifier, secret, PHI-like, and prose checks. |
customizations.py | Static checks for Copilot customization files. |
hooks.py | Hook decisions and hook logging. |
labcheck/checks.py | Lab progress checks from lab/checks.yaml. |
modelgen/* | Maintainer-only checkpoint model generator. |
"""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 theCopilot 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, andhookSpecificOutput for the VS Code Local harness. Each runtime ignores the fields it doesn't use."""
from __future__ import annotations
import jsonimport osimport reimport sysfrom datetime import UTC, datetimefrom pathlib import Path
PATH_KEYS = { "path", "paths", "file", "files",Technology and versions
Section titled “Technology and versions”| Area | Version or pin | Source |
|---|---|---|
| Node | >=20 locally, Node 22 in workflows | site/package.json, workflows |
| Astro | 7.3.2 | site/package.json |
| Starlight | 0.42.1 | site/package.json |
| Mermaid | 11.17.2 | site/package.json |
| Python package | pharmacy-demo 1.0.0, Python >=3.12 | tools/python/pyproject.toml |
| Runtime deps | duckdb, PyYAML, jsonschema | tools/python/pyproject.toml |
| Locked deps | duckdb==1.5.5, jsonschema==4.26.0, PyYAML==6.0.3 | tools/python/requirements.txt |
| Dev deps | pytest==9.1.1, ruff==0.16.7 | tools/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 |
| APM | apm-cli 0.31.0 | apm.lock.yaml, cloud setup workflow |
| Skills for Fabric | v0.3.17 | apm.yml, apm.lock.yaml |
{ "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"[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 = 110target-version = "py312"
[tool.ruff.lint]select = ["E", "F", "W", "I", "B", "UP", "PTH"]ignore = ["E501"]lockfile_version: '1'apm_version: 0.31.0dependencies:- 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