@org-design-studio/core 0.1.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/LICENSE +21 -0
- package/README.md +108 -0
- package/dist-core/core.d.mts +642 -0
- package/dist-core/core.d.ts +642 -0
- package/dist-core/core.js +2323 -0
- package/dist-core/core.js.map +1 -0
- package/dist-core/core.mjs +2280 -0
- package/dist-core/core.mjs.map +1 -0
- package/docs/API.md +202 -0
- package/package.json +36 -0
package/docs/API.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# `@org-design-studio/core` — API reference
|
|
2
|
+
|
|
3
|
+
The org-design analytics engine packaged as an embeddable TypeScript library: spans & layers
|
|
4
|
+
diagnostics, data validation and import mapping, a scenario engine with transition-cost-netted
|
|
5
|
+
economics, deterministic insight generation, and a headless org-chart layout. No React, no DOM,
|
|
6
|
+
no storage — the only runtime dependency is `d3-hierarchy`.
|
|
7
|
+
|
|
8
|
+
- Entry point: a single curated barrel (`src/lib/core.ts` in this repo; `@org-design-studio/core` when published).
|
|
9
|
+
- Build: `npm run build:core` (tsup → `dist-core/` with ESM + CJS + `.d.ts`).
|
|
10
|
+
- Publish manifest template: `packages/core-package.json`.
|
|
11
|
+
|
|
12
|
+
## Quickstart
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @org-design-studio/core
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import {
|
|
20
|
+
generateDemoData,
|
|
21
|
+
computeMetrics,
|
|
22
|
+
generateInsightsFromEmployees,
|
|
23
|
+
DEFAULT_ASSUMPTIONS,
|
|
24
|
+
} from "@org-design-studio/core";
|
|
25
|
+
|
|
26
|
+
const employees = generateDemoData(); // or your own Employee[]
|
|
27
|
+
const metrics = computeMetrics(employees, DEFAULT_ASSUMPTIONS);
|
|
28
|
+
const insights = generateInsightsFromEmployees(employees, DEFAULT_ASSUMPTIONS);
|
|
29
|
+
|
|
30
|
+
console.log(metrics.headcount, metrics.maxLayers, metrics.totalOpportunity);
|
|
31
|
+
console.log(insights[0].title, insights[0].recommendation);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Methodology guarantees
|
|
35
|
+
|
|
36
|
+
These hold for every function in the public surface and are covered by the test suite:
|
|
37
|
+
|
|
38
|
+
1. **Level-aware spans.** Narrow/wide classification uses per-level target bands
|
|
39
|
+
(`Assumptions.targetSpansByLevel`), not one global threshold. A frontline manager with 6
|
|
40
|
+
reports and an executive with 6 reports are judged against different bands; flat thresholds
|
|
41
|
+
(`narrowSpanThreshold`/`wideSpanThreshold`) apply only to unleveled roles.
|
|
42
|
+
2. **Disjoint levers.** The savings opportunities in `OrgMetrics` (delayering, span optimization,
|
|
43
|
+
duplication) are attributed to disjoint employee sets, so `totalOpportunity` never
|
|
44
|
+
double-counts a person.
|
|
45
|
+
3. **Transition-cost-netted scenarios.** `evaluateScenario` reports run-rate saving separately
|
|
46
|
+
from year-1 saving; year-1 is pro-rated by effective month (`year1CapturePct`) and netted
|
|
47
|
+
against severance/backfill/change cost (`computeTransitionCost`).
|
|
48
|
+
4. **Deterministic.** Same inputs → same outputs. No randomness, network, or clock reads in the
|
|
49
|
+
analytics path; `generateDemoData()` returns the same ~300-person org every call.
|
|
50
|
+
|
|
51
|
+
## Modules
|
|
52
|
+
|
|
53
|
+
### Data model & assumptions (`types`)
|
|
54
|
+
|
|
55
|
+
| Export | Notes |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `Employee` | The canonical input row. Required identity fields plus `level`, `fte`, `salary`, `bonus`, `total_cost`, `status`, org dimensions (`function`, `business_unit`, `location`, …). |
|
|
58
|
+
| `Assumptions` / `DEFAULT_ASSUMPTIONS` | All methodology knobs: span bands per level, target layers, saving percentages, location cost multipliers, transition assumptions, scoring weights. Start from `DEFAULT_ASSUMPTIONS` and spread overrides. |
|
|
59
|
+
| `OrgMetrics`, `OrgHealthScore`, `LayerStat`, `SpanBucket`, `DuplicateRoleGroup` | Output shapes of the analytics module. |
|
|
60
|
+
| `Scenario`, `ScenarioChange`, `ScenarioEvaluation`, `ChangeRisk`, `ScenarioScore` | Scenario engine shapes. `ScenarioChange` is a discriminated union: `move`, `remove_role`, `add_role`, `merge_teams`, `delayer`, `location_shift`, `cost_change`. |
|
|
61
|
+
| `Insight`, `InsightCategory` | Insight output shape (title, narrative, recommendation, impact $, confidence, ease, rank). |
|
|
62
|
+
| `DataQualityReport`, `DataQualityIssue`, `IssueSeverity` | Validation output shapes. |
|
|
63
|
+
| `REQUIRED_FIELDS`, `OPTIONAL_FIELDS`, `FieldMapping` | Canonical field lists for import mapping. |
|
|
64
|
+
| `MONTH_NAMES`, `DEFAULT_TITLE_NOISE_WORDS` | Methodology constants. |
|
|
65
|
+
|
|
66
|
+
### Hierarchy (`hierarchy`)
|
|
67
|
+
|
|
68
|
+
- `buildOrgTree(employees): OrgTree` — builds the reporting tree once; computes layer, span,
|
|
69
|
+
downstream headcount/cost per node, and surfaces `orphans` (manager id not found) and
|
|
70
|
+
`circular` (reporting cycles) instead of throwing. Every other module consumes this.
|
|
71
|
+
- `subtreeIds(tree, id): string[]` — an employee plus everyone below them.
|
|
72
|
+
- `deepestChains(tree, topN?)` — the longest reporting chains (for layer diagnostics).
|
|
73
|
+
- `wouldCreateCycle(tree, employeeId, newManagerId): boolean` — guard for re-parenting UIs.
|
|
74
|
+
|
|
75
|
+
### Analytics (`analytics`)
|
|
76
|
+
|
|
77
|
+
- `computeMetrics(employees, assumptions): OrgMetrics` — the headline call. Headcount/cost
|
|
78
|
+
rollups by function/BU/location/grade/layer, span buckets, layer stats, manager-of-one and
|
|
79
|
+
pure-supervisory-layer detection, AI-augmentation exposure, duplicate roles, and the three
|
|
80
|
+
disjoint savings opportunities plus `orgHealth`.
|
|
81
|
+
- `computeOrgHealth(inputs, assumptions): OrgHealthScore` — explainable 0–100 composite
|
|
82
|
+
(layer health, span health, manager ratio, management cost, duplication), each component
|
|
83
|
+
scored and weighted with a plain-language explanation. Already included in
|
|
84
|
+
`computeMetrics().orgHealth`; exported separately for custom pipelines.
|
|
85
|
+
- `spanBandForLevel(level, assumptions)` / `classifySpan(span, level, assumptions)` — the
|
|
86
|
+
level-aware span methodology, exposed directly so hosts can render their own span flags.
|
|
87
|
+
- `findDuplicateRoles(employees, assumptions)` / `normalizeTitle(title, noiseWords?)` —
|
|
88
|
+
duplication analysis with seniority-noise-stripped title normalization.
|
|
89
|
+
- `classifyAiAugmentation(employee, assumptions)` — explicit role-family mapping with a
|
|
90
|
+
conservative title-keyword fallback; augmentation potential, not an elimination judgement.
|
|
91
|
+
|
|
92
|
+
### Validation & import (`validation`)
|
|
93
|
+
|
|
94
|
+
- `validateEmployees(employees): DataQualityReport` — 0–100 quality score with banded verdict
|
|
95
|
+
and per-issue employee id lists (missing managers, cycles, duplicate ids, cost outliers, …).
|
|
96
|
+
- `autoMapColumns(columns, fields)` — fuzzy-maps a host system's column headers
|
|
97
|
+
("Employee ID", "Mgr", "Job Title", …) onto the canonical `Employee` fields.
|
|
98
|
+
- `rowsToEmployees(rows, mapping)` — coerces untyped records into typed `Employee[]` using a
|
|
99
|
+
`FieldMapping` (numbers parsed, blanks → null manager, sensible defaults).
|
|
100
|
+
|
|
101
|
+
### Scenario engine (`scenario`)
|
|
102
|
+
|
|
103
|
+
- `applyChanges(baseline, changes): ApplyResult` — pure function from a baseline plus a list of
|
|
104
|
+
`ScenarioChange`s to a future-state `Employee[]` (with `warnings` for changes that no longer
|
|
105
|
+
apply). Never mutates the baseline.
|
|
106
|
+
- `evaluateScenario(baseline, scenario, baselineMetrics, assumptions): ScenarioEvaluation` —
|
|
107
|
+
full economics: cost/headcount/layer deltas, change risk, weighted score, `runRateSaving`,
|
|
108
|
+
`transitionCost`, and `netYear1Saving = runRateSaving × year1CapturePct − transitionCost`.
|
|
109
|
+
- `computeTransitionCost(baseline, after, assumptions)` — severance by level band with country
|
|
110
|
+
multipliers, backfill percentage, and per-move one-time change cost.
|
|
111
|
+
- `year1CapturePct(assumptions)` — share of run-rate captured in year 1 given the effective month.
|
|
112
|
+
- `computeChangeRisk(...)` / `scoreScenario(...)` — the risk and scoring sub-steps, exported for
|
|
113
|
+
custom dashboards.
|
|
114
|
+
- `diffEmployees(baseline, scenario)` — moved/removed/added/changed sets between two states.
|
|
115
|
+
- `suggestConsolidations(employees, assumptions)` — machine-generated candidate actions
|
|
116
|
+
(`SuggestedAction[]`) with rationale and indicative saving.
|
|
117
|
+
|
|
118
|
+
### Insights (`insights`)
|
|
119
|
+
|
|
120
|
+
- `generateInsightsFromEmployees(employees, assumptions, scenarioEvaluations?)` — convenience:
|
|
121
|
+
metrics + insights in one call.
|
|
122
|
+
- `generateInsights(ctx: InsightContext)` — same, when you already have `OrgMetrics`.
|
|
123
|
+
- `rankInsights(insights)` — deterministic re-ranking by impact, reach, confidence, and ease;
|
|
124
|
+
sets `rank` starting at 1.
|
|
125
|
+
|
|
126
|
+
### Org-chart layout (`org-layout`)
|
|
127
|
+
|
|
128
|
+
Headless tidy-tree layout over `d3-hierarchy` — returns coordinates only, you render.
|
|
129
|
+
|
|
130
|
+
- `layoutOrg(tree, rootIds, isExpanded, opts): CanvasLayout` — positioned nodes and edges plus
|
|
131
|
+
bounds. `FULL_NODE` / `COMPACT_NODE` are ready-made `LayoutOptions`.
|
|
132
|
+
- `elbowPath(x1, y1, x2, y2): string` — SVG path string for an org-chart elbow connector.
|
|
133
|
+
- `fitToBounds(bounds, w, h, …): Viewport` / `zoomAround(vp, x, y, factor, …): Viewport` —
|
|
134
|
+
viewport math for pan/zoom canvases.
|
|
135
|
+
- `visibleNodes(nodes, vp, w, h, opts, overscan?)` — viewport culling for large orgs.
|
|
136
|
+
|
|
137
|
+
### Demo data (`demo-data`)
|
|
138
|
+
|
|
139
|
+
- `generateDemoData(): Employee[]` — deterministic ~300-person company ("Meridian Global
|
|
140
|
+
Services") with intentional structural issues; useful for demos, tests, and golden files.
|
|
141
|
+
|
|
142
|
+
### HRIS templates (`hris-templates`)
|
|
143
|
+
|
|
144
|
+
- `HRIS_TEMPLATES: HrisTemplate[]` — built-in column-name aliases and export instructions for
|
|
145
|
+
common HRIS exports (Workday, SuccessFactors, …) to seed `autoMapColumns` so a raw system
|
|
146
|
+
export maps onto the canonical model with little manual work.
|
|
147
|
+
- `getHrisTemplate(id)` — look up a template by its stable id (e.g. `"workday"`).
|
|
148
|
+
|
|
149
|
+
## Embedding as an add-on module
|
|
150
|
+
|
|
151
|
+
The intended integration for an HR platform or consultancy toolchain — keep your own UI and
|
|
152
|
+
data store; call the engine on your data:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import {
|
|
156
|
+
autoMapColumns,
|
|
157
|
+
rowsToEmployees,
|
|
158
|
+
validateEmployees,
|
|
159
|
+
computeMetrics,
|
|
160
|
+
generateInsightsFromEmployees,
|
|
161
|
+
DEFAULT_ASSUMPTIONS,
|
|
162
|
+
REQUIRED_FIELDS,
|
|
163
|
+
OPTIONAL_FIELDS,
|
|
164
|
+
type Assumptions,
|
|
165
|
+
} from "@org-design-studio/core";
|
|
166
|
+
|
|
167
|
+
// 1. Map whatever your HRIS export looks like onto the canonical model.
|
|
168
|
+
const columns = Object.keys(rawRows[0]);
|
|
169
|
+
const mapping = autoMapColumns(columns, [...REQUIRED_FIELDS, ...OPTIONAL_FIELDS]);
|
|
170
|
+
const employees = rowsToEmployees(rawRows, mapping);
|
|
171
|
+
|
|
172
|
+
// 2. Gate on data quality before showing analytics (render report.issues in your UI).
|
|
173
|
+
const report = validateEmployees(employees);
|
|
174
|
+
if (report.band === "Poor") showDataQualityScreen(report);
|
|
175
|
+
|
|
176
|
+
// 3. Tune the methodology to the client, then compute.
|
|
177
|
+
const assumptions: Assumptions = {
|
|
178
|
+
...DEFAULT_ASSUMPTIONS,
|
|
179
|
+
targetMaxLayers: 6,
|
|
180
|
+
currency: "EUR",
|
|
181
|
+
currencySymbol: "€",
|
|
182
|
+
};
|
|
183
|
+
const metrics = computeMetrics(employees, assumptions);
|
|
184
|
+
const insights = generateInsightsFromEmployees(employees, assumptions);
|
|
185
|
+
|
|
186
|
+
// 4. Render metrics.layerStats, metrics.spanBuckets, metrics.orgHealth,
|
|
187
|
+
// and the ranked insights with your own components.
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Notes for embedders:
|
|
191
|
+
|
|
192
|
+
- Everything is synchronous and pure — safe in web workers, serverless functions, or the
|
|
193
|
+
browser. For very large orgs (10k+), run `computeMetrics` off the main thread.
|
|
194
|
+
- The engine never sees the network; data residency is whatever your host app does.
|
|
195
|
+
- Deliberately **not** exported: the app's zustand store and the file/share export module
|
|
196
|
+
(DOM-dependent). State management and persistence belong to the host.
|
|
197
|
+
|
|
198
|
+
## Versioning
|
|
199
|
+
|
|
200
|
+
`0.x`: minors may adjust signatures or methodology constants (changelogged); patches are
|
|
201
|
+
additive/bug-fix only. From `1.0`: any breaking change to an exported symbol or shape is a
|
|
202
|
+
major; new exports and optional fields are minors.
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@org-design-studio/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Org-design analytics engine: spans & layers diagnostics, level-aware span classification, scenario modelling with transition-cost-netted economics, and deterministic insight generation. Pure TypeScript, no UI dependencies.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "./dist-core/core.js",
|
|
7
|
+
"module": "./dist-core/core.mjs",
|
|
8
|
+
"types": "./dist-core/core.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist-core/core.d.ts",
|
|
12
|
+
"import": "./dist-core/core.mjs",
|
|
13
|
+
"require": "./dist-core/core.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist-core",
|
|
18
|
+
"docs/API.md",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"keywords": [
|
|
23
|
+
"org-design",
|
|
24
|
+
"spans-and-layers",
|
|
25
|
+
"organization",
|
|
26
|
+
"hr-analytics",
|
|
27
|
+
"workforce-planning",
|
|
28
|
+
"scenario-modelling"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"d3-hierarchy": "^3.1.2"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
}
|
|
36
|
+
}
|