@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,371 @@
|
|
|
1
|
+
# HR Analytics - Complete Example
|
|
2
|
+
|
|
3
|
+
A comprehensive HR analytics solution for workforce planning, turnover analysis, and employee metrics.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This example demonstrates building a complete HR analytics solution using:
|
|
10
|
+
|
|
11
|
+
- **Employee-centric data model** with demographics, positions, and history
|
|
12
|
+
- **DAX measures** for headcount, turnover, tenure, and diversity metrics
|
|
13
|
+
- **Time-based analysis** for trend tracking and forecasting
|
|
14
|
+
- **Organizational hierarchy** support for drill-down reporting
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Business Requirements
|
|
19
|
+
|
|
20
|
+
### Stakeholders
|
|
21
|
+
- CHRO: Workforce strategy and executive metrics
|
|
22
|
+
- HR Business Partners: Department-level insights
|
|
23
|
+
- Talent Acquisition: Hiring pipeline and time-to-fill
|
|
24
|
+
- Compensation Team: Pay equity and benchmarking
|
|
25
|
+
- People Analytics: Deep-dive analysis
|
|
26
|
+
|
|
27
|
+
### Key Questions
|
|
28
|
+
1. What is our current headcount and how is it trending?
|
|
29
|
+
2. What's our turnover rate and what's driving attrition?
|
|
30
|
+
3. How diverse is our workforce across levels?
|
|
31
|
+
4. What's our average tenure and where are the risks?
|
|
32
|
+
5. How effective is our recruiting pipeline?
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Data Model
|
|
37
|
+
|
|
38
|
+
### Star Schema Design
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
┌─────────────────┐
|
|
42
|
+
│ DimDate │
|
|
43
|
+
│─────────────────│
|
|
44
|
+
│ DateKey (PK) │
|
|
45
|
+
│ Date │
|
|
46
|
+
│ Year │
|
|
47
|
+
│ Month │
|
|
48
|
+
│ Quarter │
|
|
49
|
+
└────────┬────────┘
|
|
50
|
+
│
|
|
51
|
+
┌─────────────────┐ │ ┌─────────────────┐
|
|
52
|
+
│ DimEmployee │ │ │ DimDepartment │
|
|
53
|
+
│─────────────────│ │ │─────────────────│
|
|
54
|
+
│ EmployeeKey(PK) │ │ │ DepartmentKey │
|
|
55
|
+
│ EmployeeID │ │ │ DepartmentName │
|
|
56
|
+
│ FirstName │ │ │ Division │
|
|
57
|
+
│ LastName │ │ │ CostCenter │
|
|
58
|
+
│ Gender │ │ │ Manager │
|
|
59
|
+
│ Ethnicity │ │ └────────┬────────┘
|
|
60
|
+
│ BirthDate │ │ │
|
|
61
|
+
│ HireDate │ │ │
|
|
62
|
+
│ TermDate │ ┌────┴─────────────────┐ │
|
|
63
|
+
│ IsActive │ │ FactEmployeeSnap │ │
|
|
64
|
+
└────────┬────────┘ │──────────────────────│ │
|
|
65
|
+
│ │ SnapshotKey (PK) │ │
|
|
66
|
+
└─────────────>│ DateKey (FK) │<┘
|
|
67
|
+
│ EmployeeKey (FK) │
|
|
68
|
+
│ DepartmentKey (FK) │
|
|
69
|
+
│ JobKey (FK) │
|
|
70
|
+
│ LocationKey (FK) │
|
|
71
|
+
│ Salary │
|
|
72
|
+
│ Bonus │
|
|
73
|
+
│ PerformanceRating │
|
|
74
|
+
│ EngagementScore │
|
|
75
|
+
└──────────┬───────────┘
|
|
76
|
+
│
|
|
77
|
+
┌──────────────┴──────────────┐
|
|
78
|
+
│ │
|
|
79
|
+
┌────────┴────────┐ ┌───────┴────────┐
|
|
80
|
+
│ DimJob │ │ DimLocation │
|
|
81
|
+
│─────────────────│ │────────────────│
|
|
82
|
+
│ JobKey (PK) │ │ LocationKey │
|
|
83
|
+
│ JobTitle │ │ LocationName │
|
|
84
|
+
│ JobFamily │ │ City │
|
|
85
|
+
│ JobLevel │ │ State │
|
|
86
|
+
│ IsManagement │ │ Country │
|
|
87
|
+
│ PayGrade │ │ Region │
|
|
88
|
+
└─────────────────┘ └────────────────┘
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Key Design Decisions
|
|
92
|
+
|
|
93
|
+
1. **Snapshot Fact Table** - Monthly snapshots capture point-in-time state
|
|
94
|
+
2. **Employee Dimension** - Contains hire/term dates for tenure calculations
|
|
95
|
+
3. **Slowly Changing Dimensions** - Track job and department changes over time
|
|
96
|
+
4. **Separate Job Dimension** - Enables job family and level analysis
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Key Measures
|
|
101
|
+
|
|
102
|
+
### Headcount Metrics
|
|
103
|
+
|
|
104
|
+
```dax
|
|
105
|
+
// Current active headcount
|
|
106
|
+
Headcount =
|
|
107
|
+
CALCULATE(
|
|
108
|
+
DISTINCTCOUNT(FactEmployeeSnap[EmployeeKey]),
|
|
109
|
+
DimEmployee[IsActive] = TRUE()
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// Headcount at end of period
|
|
113
|
+
Headcount EOP =
|
|
114
|
+
CALCULATE(
|
|
115
|
+
[Headcount],
|
|
116
|
+
LASTDATE(DimDate[Date])
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
// Average headcount for period
|
|
120
|
+
Headcount Avg =
|
|
121
|
+
AVERAGEX(
|
|
122
|
+
VALUES(DimDate[Date]),
|
|
123
|
+
[Headcount EOP]
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
// Full-Time Equivalent (if tracking FTE)
|
|
127
|
+
FTE =
|
|
128
|
+
CALCULATE(
|
|
129
|
+
SUM(FactEmployeeSnap[FTEFactor]),
|
|
130
|
+
DimEmployee[IsActive] = TRUE()
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Turnover Metrics
|
|
135
|
+
|
|
136
|
+
```dax
|
|
137
|
+
// Terminations in period
|
|
138
|
+
Terminations =
|
|
139
|
+
CALCULATE(
|
|
140
|
+
COUNTROWS(DimEmployee),
|
|
141
|
+
DimEmployee[TermDate] <> BLANK(),
|
|
142
|
+
DimEmployee[TermDate] >= MIN(DimDate[Date]),
|
|
143
|
+
DimEmployee[TermDate] <= MAX(DimDate[Date])
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
// Voluntary terminations
|
|
147
|
+
Voluntary Terms =
|
|
148
|
+
CALCULATE(
|
|
149
|
+
[Terminations],
|
|
150
|
+
DimEmployee[TermType] = "Voluntary"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
// Turnover Rate (annualized)
|
|
154
|
+
Turnover Rate =
|
|
155
|
+
VAR _Terms = [Terminations]
|
|
156
|
+
VAR _AvgHC = [Headcount Avg]
|
|
157
|
+
VAR _Months = DISTINCTCOUNT(DimDate[YearMonth])
|
|
158
|
+
RETURN
|
|
159
|
+
DIVIDE(_Terms, _AvgHC) * (12 / _Months)
|
|
160
|
+
|
|
161
|
+
// Voluntary Turnover Rate
|
|
162
|
+
Voluntary Turnover Rate =
|
|
163
|
+
VAR _VolTerms = [Voluntary Terms]
|
|
164
|
+
VAR _AvgHC = [Headcount Avg]
|
|
165
|
+
VAR _Months = DISTINCTCOUNT(DimDate[YearMonth])
|
|
166
|
+
RETURN
|
|
167
|
+
DIVIDE(_VolTerms, _AvgHC) * (12 / _Months)
|
|
168
|
+
|
|
169
|
+
// Retention Rate
|
|
170
|
+
Retention Rate =
|
|
171
|
+
1 - [Turnover Rate]
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Hiring Metrics
|
|
175
|
+
|
|
176
|
+
```dax
|
|
177
|
+
// New hires in period
|
|
178
|
+
New Hires =
|
|
179
|
+
CALCULATE(
|
|
180
|
+
COUNTROWS(DimEmployee),
|
|
181
|
+
DimEmployee[HireDate] >= MIN(DimDate[Date]),
|
|
182
|
+
DimEmployee[HireDate] <= MAX(DimDate[Date])
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
// Net change in headcount
|
|
186
|
+
Net Headcount Change =
|
|
187
|
+
[New Hires] - [Terminations]
|
|
188
|
+
|
|
189
|
+
// Hiring Rate
|
|
190
|
+
Hiring Rate =
|
|
191
|
+
DIVIDE([New Hires], [Headcount Avg])
|
|
192
|
+
|
|
193
|
+
// 90-Day Turnover (early attrition)
|
|
194
|
+
90 Day Turnover =
|
|
195
|
+
CALCULATE(
|
|
196
|
+
COUNTROWS(DimEmployee),
|
|
197
|
+
DimEmployee[TermDate] <> BLANK(),
|
|
198
|
+
DimEmployee[TermDate] >= MIN(DimDate[Date]),
|
|
199
|
+
DimEmployee[TermDate] <= MAX(DimDate[Date]),
|
|
200
|
+
DATEDIFF(DimEmployee[HireDate], DimEmployee[TermDate], DAY) <= 90
|
|
201
|
+
)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Tenure Metrics
|
|
205
|
+
|
|
206
|
+
```dax
|
|
207
|
+
// Average tenure (years)
|
|
208
|
+
Avg Tenure =
|
|
209
|
+
AVERAGEX(
|
|
210
|
+
FILTER(
|
|
211
|
+
DimEmployee,
|
|
212
|
+
DimEmployee[IsActive] = TRUE()
|
|
213
|
+
),
|
|
214
|
+
DATEDIFF(DimEmployee[HireDate], TODAY(), YEAR)
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
// Tenure distribution
|
|
218
|
+
Tenure Bucket =
|
|
219
|
+
SWITCH(
|
|
220
|
+
TRUE(),
|
|
221
|
+
[Tenure Years] < 1, "< 1 Year",
|
|
222
|
+
[Tenure Years] < 2, "1-2 Years",
|
|
223
|
+
[Tenure Years] < 5, "2-5 Years",
|
|
224
|
+
[Tenure Years] < 10, "5-10 Years",
|
|
225
|
+
">= 10 Years"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
// Flight risk (high performers with long tenure)
|
|
229
|
+
Flight Risk Count =
|
|
230
|
+
CALCULATE(
|
|
231
|
+
[Headcount],
|
|
232
|
+
FactEmployeeSnap[PerformanceRating] >= 4,
|
|
233
|
+
DimEmployee[TenureYears] >= 5,
|
|
234
|
+
FactEmployeeSnap[EngagementScore] < 3.5
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Diversity Metrics
|
|
239
|
+
|
|
240
|
+
```dax
|
|
241
|
+
// Gender diversity
|
|
242
|
+
Female % =
|
|
243
|
+
DIVIDE(
|
|
244
|
+
CALCULATE([Headcount], DimEmployee[Gender] = "Female"),
|
|
245
|
+
[Headcount]
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
// Gender diversity in management
|
|
249
|
+
Female Managers % =
|
|
250
|
+
DIVIDE(
|
|
251
|
+
CALCULATE([Headcount], DimEmployee[Gender] = "Female", DimJob[IsManagement] = TRUE()),
|
|
252
|
+
CALCULATE([Headcount], DimJob[IsManagement] = TRUE())
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
// Diversity index (simplified)
|
|
256
|
+
Diversity Index =
|
|
257
|
+
VAR _Total = [Headcount]
|
|
258
|
+
VAR _Groups =
|
|
259
|
+
ADDCOLUMNS(
|
|
260
|
+
VALUES(DimEmployee[Ethnicity]),
|
|
261
|
+
"@Count", [Headcount]
|
|
262
|
+
)
|
|
263
|
+
VAR _SumSquares =
|
|
264
|
+
SUMX(_Groups, POWER(DIVIDE([@Count], _Total), 2))
|
|
265
|
+
RETURN
|
|
266
|
+
1 - _SumSquares // Higher = more diverse
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
See [measures.dax](./measures.dax) for the complete measure library (40+ measures).
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Report Pages
|
|
274
|
+
|
|
275
|
+
### 1. Executive Dashboard
|
|
276
|
+
|
|
277
|
+
| Visual | Content |
|
|
278
|
+
|--------|---------|
|
|
279
|
+
| KPI Cards | Headcount, Turnover Rate, Avg Tenure, Diversity % |
|
|
280
|
+
| Line Chart | Headcount trend over 12 months |
|
|
281
|
+
| Stacked Bar | Hires vs Terms by month |
|
|
282
|
+
| Donut | Headcount by Division |
|
|
283
|
+
|
|
284
|
+
### 2. Turnover Analysis
|
|
285
|
+
|
|
286
|
+
| Visual | Content |
|
|
287
|
+
|--------|---------|
|
|
288
|
+
| KPI Cards | Turnover Rate, Voluntary %, Regrettable % |
|
|
289
|
+
| Waterfall | Headcount changes breakdown |
|
|
290
|
+
| Bar Chart | Turnover by Department |
|
|
291
|
+
| Table | Top term reasons with counts |
|
|
292
|
+
| Heat Map | Turnover by Tenure × Job Level |
|
|
293
|
+
|
|
294
|
+
### 3. Workforce Composition
|
|
295
|
+
|
|
296
|
+
| Visual | Content |
|
|
297
|
+
|--------|---------|
|
|
298
|
+
| Tree Map | Headcount by Division > Department |
|
|
299
|
+
| Bar Chart | Headcount by Job Level |
|
|
300
|
+
| Stacked Bar | Demographics breakdown |
|
|
301
|
+
| Map | Headcount by Location |
|
|
302
|
+
|
|
303
|
+
### 4. Tenure & Risk
|
|
304
|
+
|
|
305
|
+
| Visual | Content |
|
|
306
|
+
|--------|---------|
|
|
307
|
+
| Histogram | Tenure distribution |
|
|
308
|
+
| Scatter | Tenure vs Performance |
|
|
309
|
+
| Table | Flight risk employees |
|
|
310
|
+
| Line Chart | Avg tenure trend |
|
|
311
|
+
|
|
312
|
+
### 5. Compensation Analysis
|
|
313
|
+
|
|
314
|
+
| Visual | Content |
|
|
315
|
+
|--------|---------|
|
|
316
|
+
| Box Plot | Salary distribution by level |
|
|
317
|
+
| Scatter | Salary vs Performance |
|
|
318
|
+
| Table | Compa-ratio by job family |
|
|
319
|
+
| Bar | Pay equity by gender |
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Implementation Checklist
|
|
324
|
+
|
|
325
|
+
### Data Model
|
|
326
|
+
- [ ] Create DimDate with calendar attributes
|
|
327
|
+
- [ ] Create DimEmployee with demographics and dates
|
|
328
|
+
- [ ] Create DimDepartment with hierarchy
|
|
329
|
+
- [ ] Create DimJob with job family/level
|
|
330
|
+
- [ ] Create DimLocation with geography
|
|
331
|
+
- [ ] Load FactEmployeeSnap (monthly snapshots)
|
|
332
|
+
- [ ] Set up relationships
|
|
333
|
+
- [ ] Mark DimDate as Date Table
|
|
334
|
+
|
|
335
|
+
### Measures
|
|
336
|
+
- [ ] Create headcount measures
|
|
337
|
+
- [ ] Create turnover/retention measures
|
|
338
|
+
- [ ] Create hiring measures
|
|
339
|
+
- [ ] Create tenure measures
|
|
340
|
+
- [ ] Create diversity measures
|
|
341
|
+
- [ ] Create compensation measures
|
|
342
|
+
- [ ] Organize in display folders
|
|
343
|
+
|
|
344
|
+
### Reports
|
|
345
|
+
- [ ] Executive Dashboard
|
|
346
|
+
- [ ] Turnover Analysis with drill-through
|
|
347
|
+
- [ ] Workforce Composition
|
|
348
|
+
- [ ] Tenure & Risk Analysis
|
|
349
|
+
- [ ] Compensation Analysis
|
|
350
|
+
- [ ] Apply theme and RLS
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Files in This Example
|
|
355
|
+
|
|
356
|
+
| File | Description |
|
|
357
|
+
|------|-------------|
|
|
358
|
+
| [README.md](./README.md) | This overview |
|
|
359
|
+
| [data-model.md](./data-model.md) | Complete table definitions |
|
|
360
|
+
| [measures.dax](./measures.dax) | 40+ DAX measures |
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## HR Analytics Best Practices
|
|
365
|
+
|
|
366
|
+
1. **Use snapshots** - Point-in-time data is critical for trend analysis
|
|
367
|
+
2. **Annualize rates** - Turnover should always be annualized for comparison
|
|
368
|
+
3. **Segment analysis** - Always allow drill-down by org hierarchy
|
|
369
|
+
4. **Protect PII** - Use RLS and hide sensitive columns
|
|
370
|
+
5. **Define "turnover"** - Distinguish voluntary, involuntary, regrettable
|
|
371
|
+
6. **Benchmark** - Industry benchmarks add context to your metrics
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# HR Analytics - Data Model
|
|
2
|
+
|
|
3
|
+
Complete table definitions for the HR analytics star schema.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Fact Table
|
|
8
|
+
|
|
9
|
+
### FactEmployeeSnap
|
|
10
|
+
|
|
11
|
+
Monthly snapshot of employee state - one row per employee per month.
|
|
12
|
+
|
|
13
|
+
| Column | Data Type | Description |
|
|
14
|
+
|--------|-----------|-------------|
|
|
15
|
+
| SnapshotKey | Integer | Surrogate primary key |
|
|
16
|
+
| DateKey | Text | FK to DimDate (YYYYMM01 - first of month) |
|
|
17
|
+
| EmployeeKey | Integer | FK to DimEmployee |
|
|
18
|
+
| DepartmentKey | Integer | FK to DimDepartment |
|
|
19
|
+
| JobKey | Integer | FK to DimJob |
|
|
20
|
+
| LocationKey | Integer | FK to DimLocation |
|
|
21
|
+
| ManagerKey | Integer | FK to DimEmployee (manager) |
|
|
22
|
+
| Salary | Currency | Annual base salary |
|
|
23
|
+
| Bonus | Currency | Annual bonus target |
|
|
24
|
+
| FTEFactor | Decimal | Full-time equivalent (1.0 = full-time) |
|
|
25
|
+
| PerformanceRating | Integer | Last performance rating (1-5) |
|
|
26
|
+
| EngagementScore | Decimal | Last engagement survey score (1-5) |
|
|
27
|
+
| CompaRatio | Decimal | Salary / midpoint of pay range |
|
|
28
|
+
|
|
29
|
+
**Grain:** One row per Employee per Month (end of month snapshot).
|
|
30
|
+
|
|
31
|
+
**Relationships:**
|
|
32
|
+
- FactEmployeeSnap[DateKey] → DimDate[DateKey] (Many:1)
|
|
33
|
+
- FactEmployeeSnap[EmployeeKey] → DimEmployee[EmployeeKey] (Many:1)
|
|
34
|
+
- FactEmployeeSnap[DepartmentKey] → DimDepartment[DepartmentKey] (Many:1)
|
|
35
|
+
- FactEmployeeSnap[JobKey] → DimJob[JobKey] (Many:1)
|
|
36
|
+
- FactEmployeeSnap[LocationKey] → DimLocation[LocationKey] (Many:1)
|
|
37
|
+
- FactEmployeeSnap[ManagerKey] → DimEmployee[EmployeeKey] (Many:1, inactive)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Dimension Tables
|
|
42
|
+
|
|
43
|
+
### DimEmployee
|
|
44
|
+
|
|
45
|
+
| Column | Data Type | Description | Example |
|
|
46
|
+
|--------|-----------|-------------|---------|
|
|
47
|
+
| EmployeeKey | Integer | Surrogate PK | 10001 |
|
|
48
|
+
| EmployeeID | Text | Business key (badge #) | "E-12345" |
|
|
49
|
+
| FirstName | Text | First name | "John" |
|
|
50
|
+
| LastName | Text | Last name | "Smith" |
|
|
51
|
+
| FullName | Text | Display name | "Smith, John" |
|
|
52
|
+
| Email | Text | Work email | "jsmith@company.com" |
|
|
53
|
+
| Gender | Text | Gender identity | "Male" |
|
|
54
|
+
| Ethnicity | Text | Self-identified ethnicity | "Asian" |
|
|
55
|
+
| BirthDate | Date | Date of birth | 1985-06-15 |
|
|
56
|
+
| HireDate | Date | Original hire date | 2018-03-01 |
|
|
57
|
+
| TermDate | Date | Termination date (null if active) | NULL |
|
|
58
|
+
| TermType | Text | Termination type | "Voluntary" |
|
|
59
|
+
| TermReason | Text | Termination reason | "Career Opportunity" |
|
|
60
|
+
| IsActive | Boolean | Currently employed | TRUE |
|
|
61
|
+
| AgeYears | Integer | Current age (calculated) | 39 |
|
|
62
|
+
| TenureYears | Decimal | Years of service (calculated) | 6.8 |
|
|
63
|
+
| TenureBucket | Text | Tenure range | "5-10 Years" |
|
|
64
|
+
| Generation | Text | Generational cohort | "Millennial" |
|
|
65
|
+
|
|
66
|
+
**Termination Types:**
|
|
67
|
+
- Voluntary: Resignation, retirement
|
|
68
|
+
- Involuntary: Performance, restructuring, misconduct
|
|
69
|
+
- Other: Death, disability
|
|
70
|
+
|
|
71
|
+
**Calculated Columns:**
|
|
72
|
+
|
|
73
|
+
```dax
|
|
74
|
+
// Age
|
|
75
|
+
AgeYears =
|
|
76
|
+
DATEDIFF(DimEmployee[BirthDate], TODAY(), YEAR)
|
|
77
|
+
|
|
78
|
+
// Tenure
|
|
79
|
+
TenureYears =
|
|
80
|
+
VAR _EndDate = IF(ISBLANK(DimEmployee[TermDate]), TODAY(), DimEmployee[TermDate])
|
|
81
|
+
RETURN
|
|
82
|
+
DATEDIFF(DimEmployee[HireDate], _EndDate, DAY) / 365.25
|
|
83
|
+
|
|
84
|
+
// Tenure Bucket
|
|
85
|
+
TenureBucket =
|
|
86
|
+
SWITCH(
|
|
87
|
+
TRUE(),
|
|
88
|
+
DimEmployee[TenureYears] < 1, "< 1 Year",
|
|
89
|
+
DimEmployee[TenureYears] < 2, "1-2 Years",
|
|
90
|
+
DimEmployee[TenureYears] < 5, "2-5 Years",
|
|
91
|
+
DimEmployee[TenureYears] < 10, "5-10 Years",
|
|
92
|
+
">= 10 Years"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
// Generation
|
|
96
|
+
Generation =
|
|
97
|
+
VAR _BirthYear = YEAR(DimEmployee[BirthDate])
|
|
98
|
+
RETURN
|
|
99
|
+
SWITCH(
|
|
100
|
+
TRUE(),
|
|
101
|
+
_BirthYear >= 1997, "Gen Z",
|
|
102
|
+
_BirthYear >= 1981, "Millennial",
|
|
103
|
+
_BirthYear >= 1965, "Gen X",
|
|
104
|
+
_BirthYear >= 1946, "Boomer",
|
|
105
|
+
"Silent"
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### DimDepartment
|
|
112
|
+
|
|
113
|
+
| Column | Data Type | Description | Example |
|
|
114
|
+
|--------|-----------|-------------|---------|
|
|
115
|
+
| DepartmentKey | Integer | Surrogate PK | 201 |
|
|
116
|
+
| DepartmentCode | Text | Business key | "DEPT-ENG-001" |
|
|
117
|
+
| DepartmentName | Text | Department name | "Software Engineering" |
|
|
118
|
+
| Division | Text | Division | "Technology" |
|
|
119
|
+
| BusinessUnit | Text | Business unit | "Product Development" |
|
|
120
|
+
| CostCenter | Text | Cost center code | "CC-4500" |
|
|
121
|
+
| DepartmentHead | Text | Department leader | "Jane Doe" |
|
|
122
|
+
| ParentDepartmentKey | Integer | For hierarchy | 200 |
|
|
123
|
+
| Level | Integer | Hierarchy level | 3 |
|
|
124
|
+
| IsActive | Boolean | Currently active | TRUE |
|
|
125
|
+
|
|
126
|
+
**Hierarchies:**
|
|
127
|
+
- Division > BusinessUnit > DepartmentName
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### DimJob
|
|
132
|
+
|
|
133
|
+
| Column | Data Type | Description | Example |
|
|
134
|
+
|--------|-----------|-------------|---------|
|
|
135
|
+
| JobKey | Integer | Surrogate PK | 301 |
|
|
136
|
+
| JobCode | Text | Job code | "JOB-SWE-003" |
|
|
137
|
+
| JobTitle | Text | Job title | "Senior Software Engineer" |
|
|
138
|
+
| JobFamily | Text | Job family | "Engineering" |
|
|
139
|
+
| JobSubFamily | Text | Sub-family | "Software Development" |
|
|
140
|
+
| JobLevel | Integer | Level (1-10) | 6 |
|
|
141
|
+
| JobLevelName | Text | Level name | "Senior Individual Contributor" |
|
|
142
|
+
| IsManagement | Boolean | People manager | FALSE |
|
|
143
|
+
| IsExempt | Boolean | FLSA exempt | TRUE |
|
|
144
|
+
| PayGrade | Text | Pay grade | "G6" |
|
|
145
|
+
| PayRangeMin | Currency | Grade minimum | 120000 |
|
|
146
|
+
| PayRangeMid | Currency | Grade midpoint | 150000 |
|
|
147
|
+
| PayRangeMax | Currency | Grade maximum | 180000 |
|
|
148
|
+
|
|
149
|
+
**Job Levels:**
|
|
150
|
+
|
|
151
|
+
| Level | LevelName | IsManagement |
|
|
152
|
+
|-------|-----------|--------------|
|
|
153
|
+
| 1-3 | Entry Level | FALSE |
|
|
154
|
+
| 4-5 | Mid Level | FALSE |
|
|
155
|
+
| 6-7 | Senior Individual Contributor | FALSE |
|
|
156
|
+
| 5 | Team Lead | TRUE |
|
|
157
|
+
| 6 | Manager | TRUE |
|
|
158
|
+
| 7 | Senior Manager | TRUE |
|
|
159
|
+
| 8 | Director | TRUE |
|
|
160
|
+
| 9 | VP | TRUE |
|
|
161
|
+
| 10 | C-Suite | TRUE |
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
### DimLocation
|
|
166
|
+
|
|
167
|
+
| Column | Data Type | Description | Example |
|
|
168
|
+
|--------|-----------|-------------|---------|
|
|
169
|
+
| LocationKey | Integer | Surrogate PK | 101 |
|
|
170
|
+
| LocationCode | Text | Location code | "LOC-SF-001" |
|
|
171
|
+
| LocationName | Text | Location name | "San Francisco HQ" |
|
|
172
|
+
| Address | Text | Street address | "100 Main Street" |
|
|
173
|
+
| City | Text | City | "San Francisco" |
|
|
174
|
+
| State | Text | State/Province | "CA" |
|
|
175
|
+
| Country | Text | Country | "USA" |
|
|
176
|
+
| Region | Text | Geographic region | "North America" |
|
|
177
|
+
| Latitude | Decimal | Latitude | 37.7749 |
|
|
178
|
+
| Longitude | Decimal | Longitude | -122.4194 |
|
|
179
|
+
| IsRemote | Boolean | Remote/virtual location | FALSE |
|
|
180
|
+
| Timezone | Text | Timezone | "America/Los_Angeles" |
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### DimDate
|
|
185
|
+
|
|
186
|
+
| Column | Data Type | Description | Example |
|
|
187
|
+
|--------|-----------|-------------|---------|
|
|
188
|
+
| DateKey | Text | Primary key (YYYYMMDD) | "20240131" |
|
|
189
|
+
| Date | Date | Full date | 2024-01-31 |
|
|
190
|
+
| Year | Integer | Calendar year | 2024 |
|
|
191
|
+
| Quarter | Text | Quarter | "Q1" |
|
|
192
|
+
| Month | Integer | Month number | 1 |
|
|
193
|
+
| MonthName | Text | Month name | "January" |
|
|
194
|
+
| MonthYear | Text | Month-Year label | "Jan 2024" |
|
|
195
|
+
| YearMonth | Text | Year-Month (sortable) | "2024-01" |
|
|
196
|
+
| WeekOfYear | Integer | Week number | 5 |
|
|
197
|
+
| DayOfWeek | Integer | Day of week (1=Mon) | 3 |
|
|
198
|
+
| IsWeekend | Boolean | Weekend flag | FALSE |
|
|
199
|
+
| IsMonthEnd | Boolean | Last day of month | TRUE |
|
|
200
|
+
| IsQuarterEnd | Boolean | Last day of quarter | FALSE |
|
|
201
|
+
| IsYearEnd | Boolean | Last day of year | FALSE |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Sample Data
|
|
206
|
+
|
|
207
|
+
### DimEmployee (Selected Rows)
|
|
208
|
+
|
|
209
|
+
| EmployeeKey | EmployeeID | FullName | Gender | HireDate | TermDate | IsActive |
|
|
210
|
+
|-------------|------------|----------|--------|----------|----------|----------|
|
|
211
|
+
| 10001 | E-001 | Smith, John | Male | 2018-03-01 | NULL | TRUE |
|
|
212
|
+
| 10002 | E-002 | Johnson, Sarah | Female | 2019-06-15 | NULL | TRUE |
|
|
213
|
+
| 10003 | E-003 | Williams, Mike | Male | 2017-01-10 | 2023-08-31 | FALSE |
|
|
214
|
+
| 10004 | E-004 | Brown, Emily | Female | 2021-09-01 | NULL | TRUE |
|
|
215
|
+
| 10005 | E-005 | Davis, Alex | Non-Binary | 2022-02-14 | NULL | TRUE |
|
|
216
|
+
|
|
217
|
+
### FactEmployeeSnap (Selected Rows)
|
|
218
|
+
|
|
219
|
+
| SnapshotKey | DateKey | EmployeeKey | DepartmentKey | JobKey | Salary | PerformanceRating |
|
|
220
|
+
|-------------|---------|-------------|---------------|--------|--------|-------------------|
|
|
221
|
+
| 1 | 20240101 | 10001 | 201 | 301 | 145000 | 4 |
|
|
222
|
+
| 2 | 20240101 | 10002 | 202 | 305 | 125000 | 5 |
|
|
223
|
+
| 3 | 20240101 | 10004 | 201 | 302 | 95000 | 3 |
|
|
224
|
+
| 4 | 20240201 | 10001 | 201 | 301 | 145000 | 4 |
|
|
225
|
+
| 5 | 20240201 | 10002 | 202 | 305 | 130000 | 5 |
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Relationship Diagram
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
DimDate[DateKey] ──────────────────────────────────────┐
|
|
233
|
+
│
|
|
234
|
+
DimEmployee[EmployeeKey] ──────────────────────────┐ │
|
|
235
|
+
│ │
|
|
236
|
+
DimDepartment[DepartmentKey] ────────────────────┐ │ │
|
|
237
|
+
│ │ │
|
|
238
|
+
DimJob[JobKey] ────────────────────────────────┐ │ │ │
|
|
239
|
+
│ │ │ │
|
|
240
|
+
DimLocation[LocationKey] ────────────────────┐ │ │ │ │
|
|
241
|
+
│ │ │ │ │
|
|
242
|
+
▼ ▼ ▼ ▼ ▼
|
|
243
|
+
┌──────────────┐
|
|
244
|
+
│FactEmployee │
|
|
245
|
+
│ Snap │
|
|
246
|
+
└──────────────┘
|
|
247
|
+
|
|
248
|
+
All relationships: Many-to-One, Single direction
|
|
249
|
+
ManagerKey relationship: Inactive (activate with USERELATIONSHIP)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Data Security (RLS)
|
|
255
|
+
|
|
256
|
+
### Row-Level Security Rules
|
|
257
|
+
|
|
258
|
+
```dax
|
|
259
|
+
// HR Business Partners see their divisions only
|
|
260
|
+
[Division] = USERPRINCIPALNAME() HRBP Division Lookup
|
|
261
|
+
|
|
262
|
+
// Managers see their direct reports only
|
|
263
|
+
[ManagerKey] IN
|
|
264
|
+
CALCULATETABLE(
|
|
265
|
+
VALUES(FactEmployeeSnap[EmployeeKey]),
|
|
266
|
+
DimEmployee[Email] = USERPRINCIPALNAME()
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
// Sensitive columns hidden for non-HR users
|
|
270
|
+
// - Salary, Bonus, PerformanceRating, EngagementScore
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Power Query - Loading Employee Snapshots
|
|
276
|
+
|
|
277
|
+
```m
|
|
278
|
+
let
|
|
279
|
+
// Get current month end
|
|
280
|
+
CurrentMonthEnd = Date.EndOfMonth(DateTime.LocalNow()),
|
|
281
|
+
|
|
282
|
+
// Load from HRIS
|
|
283
|
+
Source = Sql.Database("hris-server", "HRDatabase"),
|
|
284
|
+
Employees = Source{[Schema="dbo", Item="vw_EmployeeSnapshot"]}[Data],
|
|
285
|
+
|
|
286
|
+
// Filter to active or recently termed
|
|
287
|
+
Filtered = Table.SelectRows(Employees, each
|
|
288
|
+
[TermDate] = null or [TermDate] >= Date.AddMonths(CurrentMonthEnd, -24)
|
|
289
|
+
),
|
|
290
|
+
|
|
291
|
+
// Add snapshot date key
|
|
292
|
+
WithDateKey = Table.AddColumn(Filtered, "DateKey",
|
|
293
|
+
each Date.ToText(Date.StartOfMonth([SnapshotDate]), "yyyyMMdd")
|
|
294
|
+
),
|
|
295
|
+
|
|
296
|
+
// Add calculated tenure
|
|
297
|
+
WithTenure = Table.AddColumn(WithDateKey, "TenureYears",
|
|
298
|
+
each Duration.TotalDays(
|
|
299
|
+
(if [TermDate] = null then DateTime.LocalNow() else [TermDate]) - [HireDate]
|
|
300
|
+
) / 365.25
|
|
301
|
+
)
|
|
302
|
+
in
|
|
303
|
+
WithTenure
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Performance Tips
|
|
309
|
+
|
|
310
|
+
1. **Monthly snapshots only** - Don't store daily; monthly is sufficient for HR
|
|
311
|
+
2. **Pre-calculate tenure** - Avoid complex DATEDIFF in measures
|
|
312
|
+
3. **Use integer keys** - Faster than employee IDs for joins
|
|
313
|
+
4. **Partition by year** - Enable incremental refresh
|
|
314
|
+
5. **Apply RLS early** - Filter before aggregation for performance
|
|
315
|
+
6. **Limit history** - 2-3 years of snapshots is typically enough
|