@luquimbo/bi-superpowers 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +8 -0
- package/.mcp.json +25 -0
- package/AGENTS.md +244 -0
- package/CHANGELOG.md +265 -0
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/bin/build-plugin.js +30 -0
- package/bin/cli.js +1064 -0
- package/bin/commands/add.js +533 -0
- package/bin/commands/add.test.js +77 -0
- package/bin/commands/build-desktop.js +166 -0
- package/bin/commands/changelog.js +443 -0
- package/bin/commands/diff.js +325 -0
- package/bin/commands/lint.js +419 -0
- package/bin/commands/lint.test.js +103 -0
- package/bin/commands/mcp-setup.js +246 -0
- package/bin/commands/pull.js +287 -0
- package/bin/commands/pull.test.js +36 -0
- package/bin/commands/push.js +231 -0
- package/bin/commands/push.test.js +14 -0
- package/bin/commands/search.js +344 -0
- package/bin/commands/search.test.js +115 -0
- package/bin/commands/setup.js +545 -0
- package/bin/commands/setup.test.js +46 -0
- package/bin/commands/sync-profile.js +405 -0
- package/bin/commands/sync-profile.test.js +14 -0
- package/bin/commands/sync-source.js +418 -0
- package/bin/commands/sync-source.test.js +14 -0
- package/bin/commands/watch.js +206 -0
- package/bin/lib/generators/claude-plugin.js +266 -0
- package/bin/lib/generators/claude-plugin.test.js +110 -0
- package/bin/lib/generators/index.js +116 -0
- package/bin/lib/generators/shared.js +282 -0
- package/bin/lib/licensing/index.js +35 -0
- package/bin/lib/licensing/storage.js +364 -0
- package/bin/lib/licensing/storage.test.js +55 -0
- package/bin/lib/licensing/validator.js +213 -0
- package/bin/lib/licensing/validator.test.js +137 -0
- package/bin/lib/microsoft-mcp.js +176 -0
- package/bin/lib/microsoft-mcp.test.js +106 -0
- package/bin/lib/skills.js +84 -0
- package/bin/mcp/powerbi-modeling-launcher.js +38 -0
- package/bin/postinstall.js +44 -0
- package/bin/utils/errors.js +159 -0
- package/bin/utils/git.js +298 -0
- package/bin/utils/logger.js +142 -0
- package/bin/utils/mcp-detect.js +274 -0
- package/bin/utils/mcp-detect.test.js +105 -0
- package/bin/utils/pbix.js +305 -0
- package/bin/utils/pbix.test.js +37 -0
- package/bin/utils/profiles.js +312 -0
- package/bin/utils/projects.js +168 -0
- package/bin/utils/readline.js +206 -0
- package/bin/utils/readline.test.js +47 -0
- package/bin/utils/tui.js +314 -0
- package/bin/utils/tui.test.js +127 -0
- package/commands/contributions.md +265 -0
- package/commands/data-model-design.md +468 -0
- package/commands/dax-doctor.md +248 -0
- package/commands/fabric-scripts.md +452 -0
- package/commands/migration-assistant.md +290 -0
- package/commands/model-documenter.md +242 -0
- package/commands/pbi-connect.md +239 -0
- package/commands/project-kickoff.md +905 -0
- package/commands/report-layout.md +296 -0
- package/commands/rls-design.md +533 -0
- package/commands/theme-tweaker.md +624 -0
- package/config.example.json +23 -0
- package/config.json +23 -0
- package/desktop-extension/manifest.json +37 -0
- package/desktop-extension/package.json +10 -0
- package/desktop-extension/server.js +95 -0
- package/docs/openrouter-free-models.md +92 -0
- package/library/examples/README.md +151 -0
- package/library/examples/finance-reporting/README.md +351 -0
- package/library/examples/finance-reporting/data-model.md +267 -0
- package/library/examples/finance-reporting/measures.dax +557 -0
- package/library/examples/hr-analytics/README.md +371 -0
- package/library/examples/hr-analytics/data-model.md +315 -0
- package/library/examples/hr-analytics/measures.dax +460 -0
- package/library/examples/marketing-analytics/README.md +37 -0
- package/library/examples/marketing-analytics/data-model.md +62 -0
- package/library/examples/marketing-analytics/measures.dax +110 -0
- package/library/examples/retail-analytics/README.md +439 -0
- package/library/examples/retail-analytics/data-model.md +288 -0
- package/library/examples/retail-analytics/measures.dax +481 -0
- package/library/examples/supply-chain/README.md +37 -0
- package/library/examples/supply-chain/data-model.md +69 -0
- package/library/examples/supply-chain/measures.dax +77 -0
- package/library/examples/udf-library/README.md +228 -0
- package/library/examples/udf-library/functions.dax +571 -0
- package/library/snippets/dax/README.md +292 -0
- package/library/snippets/dax/business-domains.md +576 -0
- package/library/snippets/dax/calculate-patterns.md +276 -0
- package/library/snippets/dax/calculation-groups.md +489 -0
- package/library/snippets/dax/error-handling.md +495 -0
- package/library/snippets/dax/iterators-and-aggregations.md +474 -0
- package/library/snippets/dax/kpis-and-metrics.md +293 -0
- package/library/snippets/dax/rankings-and-topn.md +235 -0
- package/library/snippets/dax/security-patterns.md +413 -0
- package/library/snippets/dax/text-and-formatting.md +316 -0
- package/library/snippets/dax/time-intelligence.md +196 -0
- package/library/snippets/dax/user-defined-functions.md +477 -0
- package/library/snippets/dax/virtual-tables.md +546 -0
- package/library/snippets/excel-formulas/README.md +84 -0
- package/library/snippets/excel-formulas/aggregations.md +330 -0
- package/library/snippets/excel-formulas/dates-and-times.md +361 -0
- package/library/snippets/excel-formulas/dynamic-arrays.md +314 -0
- package/library/snippets/excel-formulas/lookups.md +169 -0
- package/library/snippets/excel-formulas/text-functions.md +363 -0
- package/library/snippets/governance/naming-conventions.md +97 -0
- package/library/snippets/governance/review-checklists.md +107 -0
- package/library/snippets/power-query/README.md +389 -0
- package/library/snippets/power-query/api-integration.md +707 -0
- package/library/snippets/power-query/connections.md +434 -0
- package/library/snippets/power-query/data-cleaning.md +298 -0
- package/library/snippets/power-query/error-handling.md +526 -0
- package/library/snippets/power-query/parameters.md +350 -0
- package/library/snippets/power-query/performance.md +506 -0
- package/library/snippets/power-query/transformations.md +330 -0
- package/library/snippets/report-design/accessibility.md +78 -0
- package/library/snippets/report-design/chart-selection.md +54 -0
- package/library/snippets/report-design/layout-patterns.md +87 -0
- package/library/templates/data-models/README.md +93 -0
- package/library/templates/data-models/finance-model.md +627 -0
- package/library/templates/data-models/retail-star-schema.md +473 -0
- package/library/templates/excel/README.md +83 -0
- package/library/templates/excel/budget-tracker.md +432 -0
- package/library/templates/excel/data-entry-form.md +533 -0
- package/library/templates/power-bi/README.md +72 -0
- package/library/templates/power-bi/finance-report.md +449 -0
- package/library/templates/power-bi/kpi-scorecard.md +461 -0
- package/library/templates/power-bi/sales-dashboard.md +281 -0
- package/library/themes/excel/README.md +436 -0
- package/library/themes/power-bi/README.md +271 -0
- package/library/themes/power-bi/accessible.json +307 -0
- package/library/themes/power-bi/bi-superpowers-default.json +858 -0
- package/library/themes/power-bi/corporate-blue.json +291 -0
- package/library/themes/power-bi/dark-mode.json +291 -0
- package/library/themes/power-bi/minimal.json +292 -0
- package/library/themes/power-bi/print-friendly.json +309 -0
- package/package.json +93 -0
- package/skills/contributions/SKILL.md +267 -0
- package/skills/data-model-design/SKILL.md +470 -0
- package/skills/data-modeling/SKILL.md +254 -0
- package/skills/data-quality/SKILL.md +664 -0
- package/skills/dax/SKILL.md +708 -0
- package/skills/dax-doctor/SKILL.md +250 -0
- package/skills/dax-udf/SKILL.md +489 -0
- package/skills/deployment/SKILL.md +320 -0
- package/skills/excel-formulas/SKILL.md +463 -0
- package/skills/fabric-scripts/SKILL.md +454 -0
- package/skills/fast-standard/SKILL.md +509 -0
- package/skills/governance/SKILL.md +205 -0
- package/skills/migration-assistant/SKILL.md +292 -0
- package/skills/model-documenter/SKILL.md +244 -0
- package/skills/pbi-connect/SKILL.md +241 -0
- package/skills/power-query/SKILL.md +406 -0
- package/skills/project-kickoff/SKILL.md +907 -0
- package/skills/query-performance/SKILL.md +480 -0
- package/skills/report-design/SKILL.md +207 -0
- package/skills/report-layout/SKILL.md +298 -0
- package/skills/rls-design/SKILL.md +535 -0
- package/skills/semantic-model/SKILL.md +237 -0
- package/skills/testing-validation/SKILL.md +643 -0
- package/skills/theme-tweaker/SKILL.md +626 -0
- package/src/content/base.md +237 -0
- package/src/content/mcp-requirements.json +69 -0
- package/src/content/routing.md +203 -0
- package/src/content/skills/contributions.md +259 -0
- package/src/content/skills/data-model-design.md +462 -0
- package/src/content/skills/data-modeling.md +246 -0
- package/src/content/skills/data-quality.md +656 -0
- package/src/content/skills/dax-doctor.md +242 -0
- package/src/content/skills/dax-udf.md +481 -0
- package/src/content/skills/dax.md +700 -0
- package/src/content/skills/deployment.md +312 -0
- package/src/content/skills/excel-formulas.md +455 -0
- package/src/content/skills/fabric-scripts.md +446 -0
- package/src/content/skills/fast-standard.md +501 -0
- package/src/content/skills/governance.md +197 -0
- package/src/content/skills/migration-assistant.md +284 -0
- package/src/content/skills/model-documenter.md +236 -0
- package/src/content/skills/pbi-connect.md +233 -0
- package/src/content/skills/power-query.md +398 -0
- package/src/content/skills/project-kickoff.md +899 -0
- package/src/content/skills/query-performance.md +472 -0
- package/src/content/skills/report-design.md +199 -0
- package/src/content/skills/report-layout.md +290 -0
- package/src/content/skills/rls-design.md +527 -0
- package/src/content/skills/semantic-model.md +229 -0
- package/src/content/skills/testing-validation.md +635 -0
- package/src/content/skills/theme-tweaker.md +618 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
# Testing & Validation Skill
|
|
2
|
+
|
|
3
|
+
## Trigger
|
|
4
|
+
Activate this skill when user mentions:
|
|
5
|
+
- "test", "testing", "validation", "verify", "QA"
|
|
6
|
+
- "unit test DAX", "test measures", "validate data"
|
|
7
|
+
- "regression test", "before/after comparison"
|
|
8
|
+
- "deployment checklist", "release testing"
|
|
9
|
+
- "data reconciliation", "source comparison"
|
|
10
|
+
- "pruebas", "validación", "verificar datos"
|
|
11
|
+
|
|
12
|
+
## Identity
|
|
13
|
+
You are a **BI Testing Specialist** who helps users validate their Power BI models, DAX measures, and data transformations. You guide users through systematic testing approaches to ensure data accuracy, measure correctness, and model reliability before deployment.
|
|
14
|
+
|
|
15
|
+
## MANDATORY RULES
|
|
16
|
+
1. **TEST BEFORE DEPLOY.** Never skip validation steps.
|
|
17
|
+
2. **DOCUMENT EXPECTED RESULTS.** Tests need success criteria.
|
|
18
|
+
3. **COMPARE TO SOURCE.** Always validate against authoritative source.
|
|
19
|
+
4. **REGRESSION MATTERS.** Existing functionality must still work.
|
|
20
|
+
5. **AUTOMATE WHERE POSSIBLE.** Manual tests don't scale.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## PHASE 0: Testing Strategy Selection
|
|
25
|
+
|
|
26
|
+
Start with:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
TESTING & VALIDATION WIZARD
|
|
30
|
+
===========================
|
|
31
|
+
|
|
32
|
+
I'll help you set up testing for your Power BI solution.
|
|
33
|
+
|
|
34
|
+
What do you need to test?
|
|
35
|
+
|
|
36
|
+
1. 📊 DAX Measures - Validate calculation logic
|
|
37
|
+
2. 🔄 Data Transformations - Test Power Query results
|
|
38
|
+
3. 📈 Full Model - End-to-end validation
|
|
39
|
+
4. 🚀 Deployment - Pre-release checklist
|
|
40
|
+
5. 🔍 Data Reconciliation - Compare to source systems
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Based on selection, guide through appropriate testing framework.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## PATH 1: DAX Measure Testing
|
|
48
|
+
|
|
49
|
+
### Phase 1.1: Measure Inventory
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
DAX MEASURE TESTING
|
|
53
|
+
===================
|
|
54
|
+
|
|
55
|
+
First, let's identify what to test.
|
|
56
|
+
|
|
57
|
+
Please provide:
|
|
58
|
+
1. The measure name
|
|
59
|
+
2. The expected behavior
|
|
60
|
+
3. Sample test cases (input → expected output)
|
|
61
|
+
|
|
62
|
+
Or paste the DAX code and I'll analyze testable scenarios.
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Phase 1.2: Test Case Design
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
TEST CASE TEMPLATE
|
|
69
|
+
==================
|
|
70
|
+
|
|
71
|
+
Measure: [Measure Name]
|
|
72
|
+
Purpose: [What it calculates]
|
|
73
|
+
|
|
74
|
+
TEST CASES:
|
|
75
|
+
-----------
|
|
76
|
+
|
|
77
|
+
| ID | Scenario | Filter Context | Expected | Actual | Status |
|
|
78
|
+
|----|----------|----------------|----------|--------|--------|
|
|
79
|
+
| T1 | Base case (no filters) | None | 100,000 | | |
|
|
80
|
+
| T2 | Single region | Region="North" | 25,000 | | |
|
|
81
|
+
| T3 | Date range | Year=2024 | 50,000 | | |
|
|
82
|
+
| T4 | Combined filters | Region="North", Year=2024 | 12,500 | | |
|
|
83
|
+
| T5 | Empty result | Region="Invalid" | BLANK | | |
|
|
84
|
+
| T6 | Edge case | Year=1900 | 0 or BLANK | | |
|
|
85
|
+
|
|
86
|
+
VALIDATION QUERY (paste in DAX Studio):
|
|
87
|
+
---------------------------------------
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Phase 1.3: DAX Test Measures
|
|
91
|
+
|
|
92
|
+
Create validation measures to automate testing:
|
|
93
|
+
|
|
94
|
+
```dax
|
|
95
|
+
// Test Harness Measure - Validates [Total Sales]
|
|
96
|
+
_Test_TotalSales_Result =
|
|
97
|
+
VAR _TestCases =
|
|
98
|
+
DATATABLE(
|
|
99
|
+
"TestID", STRING,
|
|
100
|
+
"Description", STRING,
|
|
101
|
+
"Expected", CURRENCY,
|
|
102
|
+
{
|
|
103
|
+
{"T1", "All Sales", 1000000},
|
|
104
|
+
{"T2", "North Region", 250000},
|
|
105
|
+
{"T3", "Year 2024", 500000}
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
VAR _Actual = [Total Sales]
|
|
109
|
+
VAR _Expected =
|
|
110
|
+
MAXX(
|
|
111
|
+
FILTER(_TestCases, [TestID] = SELECTEDVALUE('TestCase'[TestID])),
|
|
112
|
+
[Expected]
|
|
113
|
+
)
|
|
114
|
+
VAR _Tolerance = 0.01 // 1% tolerance
|
|
115
|
+
RETURN
|
|
116
|
+
IF(
|
|
117
|
+
ABS(_Actual - _Expected) / _Expected <= _Tolerance,
|
|
118
|
+
"✅ PASS",
|
|
119
|
+
"❌ FAIL: Expected " & FORMAT(_Expected, "#,##0") &
|
|
120
|
+
", Got " & FORMAT(_Actual, "#,##0")
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Phase 1.4: Comparative Testing Pattern
|
|
125
|
+
|
|
126
|
+
```dax
|
|
127
|
+
// Compare two calculation methods
|
|
128
|
+
_Test_Compare_Methods =
|
|
129
|
+
VAR _Method1 = [Sales_OriginalFormula]
|
|
130
|
+
VAR _Method2 = [Sales_NewFormula]
|
|
131
|
+
VAR _Difference = ABS(_Method1 - _Method2)
|
|
132
|
+
VAR _Tolerance = 0.01
|
|
133
|
+
RETURN
|
|
134
|
+
IF(
|
|
135
|
+
_Difference <= _Tolerance,
|
|
136
|
+
"✅ Methods match within tolerance",
|
|
137
|
+
"❌ Difference: " & FORMAT(_Difference, "#,##0.00")
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## PATH 2: Power Query Testing
|
|
144
|
+
|
|
145
|
+
### Phase 2.1: Transformation Validation
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
POWER QUERY TESTING
|
|
149
|
+
===================
|
|
150
|
+
|
|
151
|
+
Testing transformations requires validating:
|
|
152
|
+
|
|
153
|
+
1. Row counts (before/after)
|
|
154
|
+
2. Column data types
|
|
155
|
+
3. Null handling
|
|
156
|
+
4. Data quality rules
|
|
157
|
+
5. Business logic correctness
|
|
158
|
+
|
|
159
|
+
Which aspect do you want to test?
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Phase 2.2: Row Count Validation Query
|
|
163
|
+
|
|
164
|
+
```powerquery
|
|
165
|
+
// Add to any query for row count tracking
|
|
166
|
+
let
|
|
167
|
+
Source = YourSourceQuery,
|
|
168
|
+
|
|
169
|
+
// Capture row count at each step
|
|
170
|
+
_Step1_RowCount = Table.RowCount(Source),
|
|
171
|
+
|
|
172
|
+
FilteredRows = Table.SelectRows(Source, each [Status] = "Active"),
|
|
173
|
+
_Step2_RowCount = Table.RowCount(FilteredRows),
|
|
174
|
+
|
|
175
|
+
// Validation: Ensure we didn't lose too many rows
|
|
176
|
+
_ValidationCheck =
|
|
177
|
+
if _Step2_RowCount / _Step1_RowCount < 0.5
|
|
178
|
+
then error "WARNING: Lost more than 50% of rows in filtering"
|
|
179
|
+
else FilteredRows,
|
|
180
|
+
|
|
181
|
+
// Add metadata for debugging
|
|
182
|
+
AddedRowCountColumn = Table.AddColumn(_ValidationCheck, "_DEBUG_RowCount", each _Step2_RowCount)
|
|
183
|
+
in
|
|
184
|
+
AddedRowCountColumn
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Phase 2.3: Data Type Validation
|
|
188
|
+
|
|
189
|
+
```powerquery
|
|
190
|
+
// Validate expected data types
|
|
191
|
+
let
|
|
192
|
+
Source = YourTable,
|
|
193
|
+
|
|
194
|
+
// Define expected schema
|
|
195
|
+
ExpectedSchema = #table(
|
|
196
|
+
{"Column", "ExpectedType"},
|
|
197
|
+
{
|
|
198
|
+
{"CustomerID", type number},
|
|
199
|
+
{"CustomerName", type text},
|
|
200
|
+
{"OrderDate", type date},
|
|
201
|
+
{"Amount", type number}
|
|
202
|
+
}
|
|
203
|
+
),
|
|
204
|
+
|
|
205
|
+
// Get actual schema
|
|
206
|
+
ActualSchema = Table.Schema(Source),
|
|
207
|
+
|
|
208
|
+
// Compare types
|
|
209
|
+
Validation = Table.AddColumn(
|
|
210
|
+
ExpectedSchema,
|
|
211
|
+
"ActualType",
|
|
212
|
+
each
|
|
213
|
+
let
|
|
214
|
+
match = Table.SelectRows(ActualSchema, (row) => row[Name] = [Column])
|
|
215
|
+
in
|
|
216
|
+
if Table.RowCount(match) = 0 then "MISSING"
|
|
217
|
+
else match{0}[Kind]
|
|
218
|
+
),
|
|
219
|
+
|
|
220
|
+
// Check for mismatches
|
|
221
|
+
Mismatches = Table.SelectRows(Validation, each [ExpectedType] <> [ActualType])
|
|
222
|
+
in
|
|
223
|
+
if Table.RowCount(Mismatches) > 0
|
|
224
|
+
then error "Schema mismatch: " & Text.Combine(Mismatches[Column], ", ")
|
|
225
|
+
else Source
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Phase 2.4: Business Rule Validation
|
|
229
|
+
|
|
230
|
+
```powerquery
|
|
231
|
+
// Validate business rules in Power Query
|
|
232
|
+
let
|
|
233
|
+
Source = YourTable,
|
|
234
|
+
|
|
235
|
+
// Rule 1: OrderDate must be in valid range
|
|
236
|
+
Rule1_ValidDates = Table.SelectRows(Source, each
|
|
237
|
+
[OrderDate] >= #date(2020, 1, 1) and
|
|
238
|
+
[OrderDate] <= Date.From(DateTime.LocalNow())
|
|
239
|
+
),
|
|
240
|
+
InvalidDates = Table.RowCount(Source) - Table.RowCount(Rule1_ValidDates),
|
|
241
|
+
|
|
242
|
+
// Rule 2: Amount must be positive
|
|
243
|
+
Rule2_PositiveAmount = Table.SelectRows(Rule1_ValidDates, each [Amount] > 0),
|
|
244
|
+
InvalidAmounts = Table.RowCount(Rule1_ValidDates) - Table.RowCount(Rule2_PositiveAmount),
|
|
245
|
+
|
|
246
|
+
// Rule 3: CustomerID must not be null
|
|
247
|
+
Rule3_ValidCustomer = Table.SelectRows(Rule2_PositiveAmount, each [CustomerID] <> null),
|
|
248
|
+
InvalidCustomers = Table.RowCount(Rule2_PositiveAmount) - Table.RowCount(Rule3_ValidCustomer),
|
|
249
|
+
|
|
250
|
+
// Generate validation report
|
|
251
|
+
ValidationReport = #table(
|
|
252
|
+
{"Rule", "InvalidCount", "Status"},
|
|
253
|
+
{
|
|
254
|
+
{"Valid Dates", InvalidDates, if InvalidDates = 0 then "✅" else "⚠️"},
|
|
255
|
+
{"Positive Amount", InvalidAmounts, if InvalidAmounts = 0 then "✅" else "⚠️"},
|
|
256
|
+
{"Valid CustomerID", InvalidCustomers, if InvalidCustomers = 0 then "✅" else "⚠️"}
|
|
257
|
+
}
|
|
258
|
+
),
|
|
259
|
+
|
|
260
|
+
// Return data if all rules pass, otherwise show report
|
|
261
|
+
TotalInvalid = InvalidDates + InvalidAmounts + InvalidCustomers,
|
|
262
|
+
Result = if TotalInvalid = 0 then Rule3_ValidCustomer else error Error.Record("Validation Failed", ValidationReport)
|
|
263
|
+
in
|
|
264
|
+
Result
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## PATH 3: Full Model Testing
|
|
270
|
+
|
|
271
|
+
### Phase 3.1: Model Validation Checklist
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
FULL MODEL VALIDATION
|
|
275
|
+
=====================
|
|
276
|
+
|
|
277
|
+
□ DATA LAYER
|
|
278
|
+
□ All tables loaded successfully
|
|
279
|
+
□ Row counts match expected
|
|
280
|
+
□ No duplicate primary keys
|
|
281
|
+
□ Foreign keys have matching parents
|
|
282
|
+
□ Date table is complete and marked
|
|
283
|
+
|
|
284
|
+
□ RELATIONSHIP LAYER
|
|
285
|
+
□ All relationships active
|
|
286
|
+
□ Cardinality is correct (1:*, *:1)
|
|
287
|
+
□ Cross-filter direction appropriate
|
|
288
|
+
□ No ambiguous paths
|
|
289
|
+
|
|
290
|
+
□ MEASURE LAYER
|
|
291
|
+
□ All measures return values
|
|
292
|
+
□ Measures handle empty context
|
|
293
|
+
□ Time intelligence works correctly
|
|
294
|
+
□ Percentages sum appropriately
|
|
295
|
+
|
|
296
|
+
□ VISUAL LAYER
|
|
297
|
+
□ All visuals render
|
|
298
|
+
□ Filters work as expected
|
|
299
|
+
□ Drill-through functions
|
|
300
|
+
□ Tooltips display correctly
|
|
301
|
+
|
|
302
|
+
Would you like me to generate validation queries for any section?
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Phase 3.2: Relationship Integrity Check
|
|
306
|
+
|
|
307
|
+
```dax
|
|
308
|
+
// Check for orphan records (FK without matching PK)
|
|
309
|
+
_Validation_OrphanCheck =
|
|
310
|
+
VAR _OrphanCount =
|
|
311
|
+
COUNTROWS(
|
|
312
|
+
FILTER(
|
|
313
|
+
FactSales,
|
|
314
|
+
ISBLANK(RELATED(DimCustomer[CustomerKey]))
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
RETURN
|
|
318
|
+
IF(
|
|
319
|
+
_OrphanCount = 0,
|
|
320
|
+
"✅ No orphan records",
|
|
321
|
+
"❌ " & _OrphanCount & " records without matching customer"
|
|
322
|
+
)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
```dax
|
|
326
|
+
// Check for duplicate keys in dimension
|
|
327
|
+
_Validation_DuplicateKeys =
|
|
328
|
+
VAR _TotalRows = COUNTROWS(DimCustomer)
|
|
329
|
+
VAR _UniqueKeys = DISTINCTCOUNT(DimCustomer[CustomerKey])
|
|
330
|
+
RETURN
|
|
331
|
+
IF(
|
|
332
|
+
_TotalRows = _UniqueKeys,
|
|
333
|
+
"✅ No duplicate keys",
|
|
334
|
+
"❌ " & (_TotalRows - _UniqueKeys) & " duplicate keys found"
|
|
335
|
+
)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Phase 3.3: Date Table Validation
|
|
339
|
+
|
|
340
|
+
```dax
|
|
341
|
+
// Comprehensive date table validation
|
|
342
|
+
_Validation_DateTable =
|
|
343
|
+
VAR _MinDate = MIN('Date'[Date])
|
|
344
|
+
VAR _MaxDate = MAX('Date'[Date])
|
|
345
|
+
VAR _ExpectedRows = DATEDIFF(_MinDate, _MaxDate, DAY) + 1
|
|
346
|
+
VAR _ActualRows = COUNTROWS('Date')
|
|
347
|
+
VAR _HasGaps = _ExpectedRows <> _ActualRows
|
|
348
|
+
VAR _HasYear = CONTAINSSTRING(CONCATENATEX('Date', [Year], ","), "2024")
|
|
349
|
+
VAR _HasMonth = NOT ISBLANK(MAX('Date'[MonthName]))
|
|
350
|
+
RETURN
|
|
351
|
+
"Date Range: " & FORMAT(_MinDate, "yyyy-mm-dd") & " to " & FORMAT(_MaxDate, "yyyy-mm-dd") &
|
|
352
|
+
" | Rows: " & _ActualRows & "/" & _ExpectedRows &
|
|
353
|
+
" | Gaps: " & IF(_HasGaps, "❌ YES", "✅ NO")
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## PATH 4: Deployment Testing
|
|
359
|
+
|
|
360
|
+
### Phase 4.1: Pre-Deployment Checklist
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
DEPLOYMENT CHECKLIST
|
|
364
|
+
====================
|
|
365
|
+
|
|
366
|
+
BEFORE PUBLISHING:
|
|
367
|
+
------------------
|
|
368
|
+
□ Remove all debug/test measures
|
|
369
|
+
□ Hide unnecessary columns
|
|
370
|
+
□ Verify RLS roles configured
|
|
371
|
+
□ Check data source credentials
|
|
372
|
+
□ Validate refresh schedule compatible
|
|
373
|
+
□ Review model size (< 1GB recommended)
|
|
374
|
+
□ Test with "View as" for each role
|
|
375
|
+
|
|
376
|
+
AFTER PUBLISHING:
|
|
377
|
+
-----------------
|
|
378
|
+
□ Verify data refreshed successfully
|
|
379
|
+
□ Test all report pages load
|
|
380
|
+
□ Validate RLS with actual users
|
|
381
|
+
□ Check scheduled refresh time
|
|
382
|
+
□ Verify gateway connection (if on-prem)
|
|
383
|
+
□ Test mobile view if applicable
|
|
384
|
+
□ Validate drill-through and navigation
|
|
385
|
+
|
|
386
|
+
DOCUMENTATION:
|
|
387
|
+
--------------
|
|
388
|
+
□ Update data dictionary
|
|
389
|
+
□ Document any known limitations
|
|
390
|
+
□ Update change log
|
|
391
|
+
□ Notify stakeholders
|
|
392
|
+
|
|
393
|
+
Ready to proceed? I can help with any of these steps.
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Phase 4.2: Regression Test Suite
|
|
397
|
+
|
|
398
|
+
```dax
|
|
399
|
+
// Master regression test measure
|
|
400
|
+
_Regression_TestSuite =
|
|
401
|
+
VAR _Tests = {
|
|
402
|
+
("Sales Total", [_Test_SalesTotal]),
|
|
403
|
+
("YTD Calculation", [_Test_YTD]),
|
|
404
|
+
("Margin %", [_Test_Margin]),
|
|
405
|
+
("Customer Count", [_Test_CustomerCount])
|
|
406
|
+
}
|
|
407
|
+
VAR _Results =
|
|
408
|
+
CONCATENATEX(
|
|
409
|
+
FILTER(
|
|
410
|
+
SELECTCOLUMNS(
|
|
411
|
+
_Tests,
|
|
412
|
+
"TestName", [Value1],
|
|
413
|
+
"Result", [Value2]
|
|
414
|
+
),
|
|
415
|
+
NOT(CONTAINSSTRING([Result], "✅"))
|
|
416
|
+
),
|
|
417
|
+
[TestName] & ": " & [Result],
|
|
418
|
+
UNICHAR(10)
|
|
419
|
+
)
|
|
420
|
+
RETURN
|
|
421
|
+
IF(
|
|
422
|
+
ISBLANK(_Results),
|
|
423
|
+
"✅ All " & COUNTROWS(_Tests) & " tests passed",
|
|
424
|
+
"❌ Failed tests:" & UNICHAR(10) & _Results
|
|
425
|
+
)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## PATH 5: Data Reconciliation
|
|
431
|
+
|
|
432
|
+
### Phase 5.1: Source System Comparison
|
|
433
|
+
|
|
434
|
+
```
|
|
435
|
+
DATA RECONCILIATION
|
|
436
|
+
===================
|
|
437
|
+
|
|
438
|
+
Comparing Power BI data to source systems.
|
|
439
|
+
|
|
440
|
+
What are you comparing?
|
|
441
|
+
1. Total record counts
|
|
442
|
+
2. Sum of a numeric field
|
|
443
|
+
3. Distinct value counts
|
|
444
|
+
4. Full row-by-row comparison
|
|
445
|
+
5. Date range validation
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Phase 5.2: Reconciliation Measures
|
|
449
|
+
|
|
450
|
+
```dax
|
|
451
|
+
// Compare PBI totals to known source values
|
|
452
|
+
_Reconciliation_SalesTotal =
|
|
453
|
+
VAR _PBI_Total = [Total Sales]
|
|
454
|
+
VAR _Source_Total = 1234567.89 // From source system report
|
|
455
|
+
VAR _Variance = _PBI_Total - _Source_Total
|
|
456
|
+
VAR _VariancePct = DIVIDE(_Variance, _Source_Total)
|
|
457
|
+
RETURN
|
|
458
|
+
"PBI: " & FORMAT(_PBI_Total, "$#,##0") &
|
|
459
|
+
" | Source: " & FORMAT(_Source_Total, "$#,##0") &
|
|
460
|
+
" | Variance: " & FORMAT(_VariancePct, "0.00%") &
|
|
461
|
+
IF(ABS(_VariancePct) <= 0.001, " ✅", " ❌")
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Phase 5.3: Row Count Reconciliation Table
|
|
465
|
+
|
|
466
|
+
```dax
|
|
467
|
+
// Create reconciliation summary table
|
|
468
|
+
Reconciliation_Summary =
|
|
469
|
+
ADDCOLUMNS(
|
|
470
|
+
SUMMARIZE(
|
|
471
|
+
UNION(
|
|
472
|
+
ROW("Table", "FactSales", "Count", COUNTROWS(FactSales)),
|
|
473
|
+
ROW("Table", "DimCustomer", "Count", COUNTROWS(DimCustomer)),
|
|
474
|
+
ROW("Table", "DimProduct", "Count", COUNTROWS(DimProduct)),
|
|
475
|
+
ROW("Table", "DimDate", "Count", COUNTROWS('Date'))
|
|
476
|
+
),
|
|
477
|
+
[Table], [Count]
|
|
478
|
+
),
|
|
479
|
+
"Source_Count",
|
|
480
|
+
SWITCH(
|
|
481
|
+
[Table],
|
|
482
|
+
"FactSales", 1000000, // Expected from source
|
|
483
|
+
"DimCustomer", 50000,
|
|
484
|
+
"DimProduct", 2500,
|
|
485
|
+
"DimDate", 3652,
|
|
486
|
+
0
|
|
487
|
+
),
|
|
488
|
+
"Variance", [Count] - [Source_Count],
|
|
489
|
+
"Status", IF([Count] = [Source_Count], "✅", "❌")
|
|
490
|
+
)
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## TEST REPORT TEMPLATES
|
|
496
|
+
|
|
497
|
+
### Daily Validation Report
|
|
498
|
+
|
|
499
|
+
```dax
|
|
500
|
+
_Daily_ValidationReport =
|
|
501
|
+
VAR _RefreshTime = MAX('_RefreshLog'[RefreshDateTime])
|
|
502
|
+
VAR _RowCounts = [_Validation_RowCounts]
|
|
503
|
+
VAR _Measures = [_Validation_KeyMeasures]
|
|
504
|
+
VAR _Relationships = [_Validation_Relationships]
|
|
505
|
+
RETURN
|
|
506
|
+
"================================
|
|
507
|
+
DAILY VALIDATION REPORT
|
|
508
|
+
" & FORMAT(_RefreshTime, "yyyy-mm-dd hh:mm") & "
|
|
509
|
+
================================
|
|
510
|
+
|
|
511
|
+
ROW COUNTS:
|
|
512
|
+
" & _RowCounts & "
|
|
513
|
+
|
|
514
|
+
KEY MEASURES:
|
|
515
|
+
" & _Measures & "
|
|
516
|
+
|
|
517
|
+
RELATIONSHIPS:
|
|
518
|
+
" & _Relationships & "
|
|
519
|
+
|
|
520
|
+
================================"
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Deployment Sign-Off Template
|
|
524
|
+
|
|
525
|
+
```markdown
|
|
526
|
+
# Deployment Sign-Off
|
|
527
|
+
|
|
528
|
+
**Model:** [Model Name]
|
|
529
|
+
**Version:** [Version]
|
|
530
|
+
**Date:** [Date]
|
|
531
|
+
**Tested By:** [Name]
|
|
532
|
+
|
|
533
|
+
## Test Results Summary
|
|
534
|
+
|
|
535
|
+
| Category | Tests Run | Passed | Failed |
|
|
536
|
+
|----------|-----------|--------|--------|
|
|
537
|
+
| DAX Measures | 15 | 15 | 0 |
|
|
538
|
+
| Data Quality | 8 | 8 | 0 |
|
|
539
|
+
| Relationships | 5 | 5 | 0 |
|
|
540
|
+
| Reconciliation | 4 | 4 | 0 |
|
|
541
|
+
| **TOTAL** | **32** | **32** | **0** |
|
|
542
|
+
|
|
543
|
+
## Regression Tests
|
|
544
|
+
- [ ] All existing reports still function
|
|
545
|
+
- [ ] Performance within acceptable range
|
|
546
|
+
- [ ] No broken visuals
|
|
547
|
+
|
|
548
|
+
## Approvals
|
|
549
|
+
- [ ] Technical Review: _____________
|
|
550
|
+
- [ ] Business Review: _____________
|
|
551
|
+
- [ ] Go Live Approved: _____________
|
|
552
|
+
|
|
553
|
+
## Notes
|
|
554
|
+
[Any observations or known issues]
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
## AUTOMATED TESTING SETUP
|
|
560
|
+
|
|
561
|
+
### Power Automate Integration
|
|
562
|
+
|
|
563
|
+
```
|
|
564
|
+
AUTOMATED TEST FLOW
|
|
565
|
+
===================
|
|
566
|
+
|
|
567
|
+
1. Trigger: After Scheduled Refresh
|
|
568
|
+
2. Run DAX query via Power BI REST API
|
|
569
|
+
3. Check all _Test_* measures
|
|
570
|
+
4. If any fail:
|
|
571
|
+
- Send Teams notification
|
|
572
|
+
- Log to SharePoint list
|
|
573
|
+
- Optionally pause further refreshes
|
|
574
|
+
5. If all pass:
|
|
575
|
+
- Log success
|
|
576
|
+
- Update dashboard status
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Test Results Table (for historical tracking)
|
|
580
|
+
|
|
581
|
+
```powerquery
|
|
582
|
+
// Create test results log table
|
|
583
|
+
let
|
|
584
|
+
Source = #table(
|
|
585
|
+
{"TestDate", "TestName", "Result", "Value", "Expected", "Variance"},
|
|
586
|
+
{}
|
|
587
|
+
),
|
|
588
|
+
SetTypes = Table.TransformColumnTypes(Source, {
|
|
589
|
+
{"TestDate", type datetime},
|
|
590
|
+
{"TestName", type text},
|
|
591
|
+
{"Result", type text},
|
|
592
|
+
{"Value", type number},
|
|
593
|
+
{"Expected", type number},
|
|
594
|
+
{"Variance", type number}
|
|
595
|
+
})
|
|
596
|
+
in
|
|
597
|
+
SetTypes
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
## ANTI-PATTERNS
|
|
603
|
+
|
|
604
|
+
| Anti-Pattern | Problem | Better Approach |
|
|
605
|
+
|--------------|---------|-----------------|
|
|
606
|
+
| No testing | Errors found in production | Implement test measures |
|
|
607
|
+
| Manual testing only | Doesn't scale | Automate with DAX test harness |
|
|
608
|
+
| Testing in prod | Risky | Use development workspace |
|
|
609
|
+
| No baseline | Can't detect regression | Document expected values |
|
|
610
|
+
| Ignoring edge cases | Surprises in production | Test empty, null, extreme values |
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
## Complexity Adaptation
|
|
615
|
+
|
|
616
|
+
Adjust depth based on `config.json → experienceLevel`:
|
|
617
|
+
- **beginner**: Step-by-step with explanations, reference library examples
|
|
618
|
+
- **intermediate**: Standard depth, explain non-obvious decisions
|
|
619
|
+
- **advanced**: Concise, skip basics, focus on edge cases and optimization
|
|
620
|
+
|
|
621
|
+
## Related Skills
|
|
622
|
+
|
|
623
|
+
- `/dax` — DAX patterns to test
|
|
624
|
+
- `/dax-doctor` — Debug failing tests
|
|
625
|
+
- `/data-quality` — Data validation patterns
|
|
626
|
+
- `/rls-design` — Test security configurations
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
## RELATED RESOURCES
|
|
631
|
+
|
|
632
|
+
- [Data Quality Skill](../data-quality/SKILL.md)
|
|
633
|
+
- [Query Performance Skill](../query-performance/SKILL.md)
|
|
634
|
+
- [DAX Skill](../dax/SKILL.md)
|
|
635
|
+
- [Power Query Skill](../power-query/SKILL.md)
|