business-as-code 2.1.3 → 2.4.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/.turbo/turbo-build.log +4 -5
- package/CHANGELOG.md +53 -0
- package/README.md +2 -0
- package/dist/dollar.d.ts.map +1 -1
- package/dist/dollar.js +2 -2
- package/dist/dollar.js.map +1 -1
- package/dist/entities/organization.d.ts +4 -0
- package/dist/entities/organization.d.ts.map +1 -1
- package/dist/entities/organization.js +27 -18
- package/dist/entities/organization.js.map +1 -1
- package/dist/entities/planning.d.ts +87 -0
- package/dist/finance/account.d.ts +44 -0
- package/dist/finance/account.d.ts.map +1 -0
- package/dist/finance/account.js +6 -0
- package/dist/finance/account.js.map +1 -0
- package/dist/finance/authority.d.ts +78 -0
- package/dist/finance/authority.d.ts.map +1 -0
- package/dist/finance/authority.js +27 -0
- package/dist/finance/authority.js.map +1 -0
- package/dist/finance/card.d.ts +36 -0
- package/dist/finance/card.d.ts.map +1 -0
- package/dist/finance/card.js +6 -0
- package/dist/finance/card.js.map +1 -0
- package/dist/finance/identity.d.ts +30 -0
- package/dist/finance/identity.d.ts.map +1 -0
- package/dist/finance/identity.js +8 -0
- package/dist/finance/identity.js.map +1 -0
- package/dist/finance/index.d.ts +36 -0
- package/dist/finance/index.d.ts.map +1 -0
- package/dist/finance/index.js +22 -0
- package/dist/finance/index.js.map +1 -0
- package/dist/finance/ledger.d.ts +24 -0
- package/dist/finance/ledger.d.ts.map +1 -0
- package/dist/finance/ledger.js +8 -0
- package/dist/finance/ledger.js.map +1 -0
- package/dist/finance/merchant.d.ts +129 -0
- package/dist/finance/merchant.d.ts.map +1 -0
- package/dist/finance/merchant.js +21 -0
- package/dist/finance/merchant.js.map +1 -0
- package/dist/finance/outcome-contract.d.ts +139 -0
- package/dist/finance/outcome-contract.d.ts.map +1 -0
- package/dist/finance/outcome-contract.js +27 -0
- package/dist/finance/outcome-contract.js.map +1 -0
- package/dist/finance/port.d.ts +121 -0
- package/dist/finance/port.d.ts.map +1 -0
- package/dist/finance/port.js +10 -0
- package/dist/finance/port.js.map +1 -0
- package/dist/finance/pricing.d.ts +154 -0
- package/dist/finance/pricing.d.ts.map +1 -0
- package/dist/finance/pricing.js +79 -0
- package/dist/finance/pricing.js.map +1 -0
- package/dist/finance/proof-predicate.d.ts +92 -0
- package/dist/finance/proof-predicate.d.ts.map +1 -0
- package/dist/finance/proof-predicate.js +80 -0
- package/dist/finance/proof-predicate.js.map +1 -0
- package/dist/finance/refund.d.ts +44 -0
- package/dist/finance/refund.d.ts.map +1 -0
- package/dist/finance/refund.js +41 -0
- package/dist/finance/refund.js.map +1 -0
- package/dist/finance/sla.d.ts +25 -0
- package/dist/finance/sla.d.ts.map +1 -0
- package/dist/finance/sla.js +7 -0
- package/dist/finance/sla.js.map +1 -0
- package/dist/finance/types.d.ts +79 -0
- package/dist/finance/types.d.ts.map +1 -0
- package/dist/finance/types.js +8 -0
- package/dist/{canvas → finance}/types.js.map +1 -1
- package/dist/goals.d.ts +19 -0
- package/dist/goals.d.ts.map +1 -1
- package/dist/goals.js +81 -12
- package/dist/goals.js.map +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -7
- package/dist/index.js.map +1 -1
- package/dist/kpis.d.ts +19 -0
- package/dist/kpis.d.ts.map +1 -1
- package/dist/kpis.js +71 -6
- package/dist/kpis.js.map +1 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +29 -24
- package/dist/metrics.js.map +1 -1
- package/dist/okrs.d.ts +34 -0
- package/dist/okrs.d.ts.map +1 -1
- package/dist/okrs.js +135 -13
- package/dist/okrs.js.map +1 -1
- package/dist/organization.d.ts.map +1 -1
- package/dist/organization.js +11 -11
- package/dist/organization.js.map +1 -1
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +13 -12
- package/dist/process.js.map +1 -1
- package/dist/product.d.ts.map +1 -1
- package/dist/product.js +9 -9
- package/dist/product.js.map +1 -1
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +194 -32
- package/dist/queries.js.map +1 -1
- package/dist/roles.d.ts +25 -31
- package/dist/roles.d.ts.map +1 -1
- package/dist/roles.js +37 -10
- package/dist/roles.js.map +1 -1
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +13 -12
- package/dist/workflow.js.map +1 -1
- package/package.json +20 -13
- package/src/dollar.ts +5 -2
- package/src/entities/organization.ts +31 -18
- package/src/finance/account.ts +48 -0
- package/src/finance/authority.ts +42 -0
- package/src/finance/card.ts +38 -0
- package/src/finance/identity.ts +31 -0
- package/src/finance/index.ts +117 -0
- package/src/finance/ledger.ts +26 -0
- package/src/finance/merchant.ts +127 -0
- package/src/finance/outcome-contract.ts +157 -0
- package/src/finance/port.ts +144 -0
- package/src/finance/pricing.ts +197 -0
- package/src/finance/proof-predicate.ts +106 -0
- package/src/finance/refund.ts +52 -0
- package/src/finance/sla.ts +33 -0
- package/src/finance/types.ts +75 -0
- package/src/goals.ts +78 -12
- package/src/index.ts +48 -18
- package/src/kpis.ts +62 -8
- package/src/metrics.ts +92 -79
- package/src/okrs.ts +120 -20
- package/src/organization.ts +12 -15
- package/src/process.ts +11 -12
- package/src/product.ts +8 -9
- package/src/queries.ts +238 -75
- package/src/roles.ts +62 -61
- package/src/workflow.ts +22 -15
- package/test/business.test.ts +282 -0
- package/test/dollar.test.ts +270 -0
- package/test/entities.test.ts +628 -0
- package/test/financials.test.ts +539 -0
- package/test/goals.test.ts +451 -0
- package/{src → test}/index.test.ts +1 -1
- package/test/kpis.test.ts +440 -0
- package/test/metrics.test.ts +744 -0
- package/test/okrs.test.ts +741 -0
- package/test/organization.test.ts +548 -0
- package/test/process.test.ts +503 -0
- package/test/product.test.ts +430 -0
- package/test/queries.test.ts +556 -0
- package/test/roles.test.ts +546 -0
- package/test/service.test.ts +450 -0
- package/test/types.test.ts +1141 -0
- package/test/vision.test.ts +214 -0
- package/test/workflow.test.ts +501 -0
- package/vitest.config.ts +47 -0
- package/LICENSE +0 -21
- package/dist/canvas/activities.d.ts +0 -19
- package/dist/canvas/activities.d.ts.map +0 -1
- package/dist/canvas/activities.js +0 -20
- package/dist/canvas/activities.js.map +0 -1
- package/dist/canvas/channels.d.ts +0 -20
- package/dist/canvas/channels.d.ts.map +0 -1
- package/dist/canvas/channels.js +0 -21
- package/dist/canvas/channels.js.map +0 -1
- package/dist/canvas/relationships.d.ts +0 -20
- package/dist/canvas/relationships.d.ts.map +0 -1
- package/dist/canvas/relationships.js +0 -21
- package/dist/canvas/relationships.js.map +0 -1
- package/dist/canvas/resources.d.ts +0 -20
- package/dist/canvas/resources.d.ts.map +0 -1
- package/dist/canvas/resources.js +0 -30
- package/dist/canvas/resources.js.map +0 -1
- package/dist/canvas/revenue.d.ts +0 -22
- package/dist/canvas/revenue.d.ts.map +0 -1
- package/dist/canvas/revenue.js +0 -30
- package/dist/canvas/revenue.js.map +0 -1
- package/dist/canvas/segments.d.ts +0 -20
- package/dist/canvas/segments.d.ts.map +0 -1
- package/dist/canvas/segments.js +0 -28
- package/dist/canvas/segments.js.map +0 -1
- package/dist/canvas/types.d.ts +0 -232
- package/dist/canvas/types.d.ts.map +0 -1
- package/dist/canvas/types.js +0 -8
- package/dist/canvas/value.d.ts +0 -20
- package/dist/canvas/value.d.ts.map +0 -1
- package/dist/canvas/value.js +0 -21
- package/dist/canvas/value.js.map +0 -1
- package/src/business.js +0 -108
- package/src/canvas/activities.ts +0 -32
- package/src/canvas/canvas.ts +0 -482
- package/src/canvas/channels.ts +0 -34
- package/src/canvas/costs.ts +0 -43
- package/src/canvas/economics.ts +0 -99
- package/src/canvas/index.ts +0 -206
- package/src/canvas/partnerships.ts +0 -34
- package/src/canvas/projections.ts +0 -141
- package/src/canvas/relationships.ts +0 -34
- package/src/canvas/resources.ts +0 -43
- package/src/canvas/revenue.ts +0 -56
- package/src/canvas/segments.ts +0 -42
- package/src/canvas/types.ts +0 -363
- package/src/canvas/value.ts +0 -34
- package/src/dollar.js +0 -106
- package/src/entities/assets.js +0 -322
- package/src/entities/business.js +0 -369
- package/src/entities/communication.js +0 -254
- package/src/entities/customers.js +0 -988
- package/src/entities/financials.js +0 -931
- package/src/entities/goals.js +0 -799
- package/src/entities/index.js +0 -197
- package/src/entities/legal.js +0 -300
- package/src/entities/market.js +0 -300
- package/src/entities/marketing.js +0 -1156
- package/src/entities/offerings.js +0 -726
- package/src/entities/operations.js +0 -786
- package/src/entities/organization.js +0 -806
- package/src/entities/partnerships.js +0 -299
- package/src/entities/planning.js +0 -270
- package/src/entities/projects.js +0 -348
- package/src/entities/risk.js +0 -292
- package/src/entities/sales.js +0 -1247
- package/src/financials.js +0 -296
- package/src/goals.js +0 -214
- package/src/index.js +0 -131
- package/src/index.test.js +0 -274
- package/src/kpis.js +0 -231
- package/src/metrics.js +0 -324
- package/src/okrs.js +0 -268
- package/src/organization.js +0 -172
- package/src/process.js +0 -240
- package/src/product.js +0 -144
- package/src/queries.js +0 -414
- package/src/roles.js +0 -254
- package/src/service.js +0 -139
- package/src/types.js +0 -4
- package/src/vision.js +0 -67
- package/src/workflow.js +0 -246
- package/tests/canvas.test.ts +0 -842
package/src/index.ts
CHANGED
|
@@ -114,12 +114,7 @@ export {
|
|
|
114
114
|
} from './business.js'
|
|
115
115
|
|
|
116
116
|
// Export vision functions
|
|
117
|
-
export {
|
|
118
|
-
Vision,
|
|
119
|
-
checkIndicator,
|
|
120
|
-
calculateProgress,
|
|
121
|
-
validateVision,
|
|
122
|
-
} from './vision.js'
|
|
117
|
+
export { Vision, checkIndicator, calculateProgress, validateVision } from './vision.js'
|
|
123
118
|
|
|
124
119
|
// Export goal functions
|
|
125
120
|
export {
|
|
@@ -136,6 +131,22 @@ export {
|
|
|
136
131
|
hasCircularDependencies,
|
|
137
132
|
sortByDependencies,
|
|
138
133
|
validateGoals,
|
|
134
|
+
// org.ai conversions
|
|
135
|
+
toOrgGoal,
|
|
136
|
+
fromOrgGoal,
|
|
137
|
+
} from './goals.js'
|
|
138
|
+
|
|
139
|
+
// Re-export org.ai goal types
|
|
140
|
+
export type {
|
|
141
|
+
OrgGoal,
|
|
142
|
+
OrgGoals,
|
|
143
|
+
GoalStatus,
|
|
144
|
+
GoalCategory,
|
|
145
|
+
GoalPriority,
|
|
146
|
+
// Aliased versions for backward compatibility
|
|
147
|
+
GoalStatus as OrgGoalStatus,
|
|
148
|
+
GoalCategory as OrgGoalCategory,
|
|
149
|
+
GoalPriority as OrgGoalPriority,
|
|
139
150
|
} from './goals.js'
|
|
140
151
|
|
|
141
152
|
// Export product functions
|
|
@@ -218,8 +229,14 @@ export {
|
|
|
218
229
|
formatValue,
|
|
219
230
|
comparePerformance,
|
|
220
231
|
validateKPIs,
|
|
232
|
+
// org.ai conversions
|
|
233
|
+
toOrgKPI,
|
|
234
|
+
fromOrgKPI,
|
|
221
235
|
} from './kpis.js'
|
|
222
236
|
|
|
237
|
+
// Re-export org.ai KPI types
|
|
238
|
+
export type { OrgKPI, KPICategory, KPITrend, KPIFrequency, KPIHistoryEntry } from './kpis.js'
|
|
239
|
+
|
|
223
240
|
// Export OKR functions
|
|
224
241
|
export {
|
|
225
242
|
okrs,
|
|
@@ -239,8 +256,16 @@ export {
|
|
|
239
256
|
formatKeyResult,
|
|
240
257
|
compareOKRPerformance,
|
|
241
258
|
validateOKRs,
|
|
259
|
+
// org.ai conversions
|
|
260
|
+
toOrgOKR,
|
|
261
|
+
fromOrgOKR,
|
|
262
|
+
toOrgKeyResult,
|
|
263
|
+
fromOrgKeyResult,
|
|
242
264
|
} from './okrs.js'
|
|
243
265
|
|
|
266
|
+
// Re-export org.ai OKR types
|
|
267
|
+
export type { OrgOKR, OrgKeyResult, OKRStatus, KeyResultStatus } from './okrs.js'
|
|
268
|
+
|
|
244
269
|
// Export financial functions
|
|
245
270
|
export {
|
|
246
271
|
financials,
|
|
@@ -272,13 +297,7 @@ export {
|
|
|
272
297
|
} from './financials.js'
|
|
273
298
|
|
|
274
299
|
// Export $ helper and context management
|
|
275
|
-
export {
|
|
276
|
-
$,
|
|
277
|
-
createBusinessOperations,
|
|
278
|
-
updateContext,
|
|
279
|
-
getContext,
|
|
280
|
-
resetContext,
|
|
281
|
-
} from './dollar.js'
|
|
300
|
+
export { $, createBusinessOperations, updateContext, getContext, resetContext } from './dollar.js'
|
|
282
301
|
|
|
283
302
|
// Export SaaS metrics
|
|
284
303
|
export type {
|
|
@@ -419,8 +438,15 @@ export type {
|
|
|
419
438
|
AssigneeRef,
|
|
420
439
|
TaskRoutingRule,
|
|
421
440
|
WorkflowRole,
|
|
441
|
+
// Re-exported from org.ai and digital-workers
|
|
442
|
+
OrgRole,
|
|
443
|
+
OrgRoleType,
|
|
444
|
+
RoleWorkerType,
|
|
422
445
|
} from './roles.js'
|
|
423
446
|
|
|
447
|
+
// Re-export Worker types from digital-workers via roles.ts
|
|
448
|
+
export type { Worker, WorkerRef } from './roles.js'
|
|
449
|
+
|
|
424
450
|
export {
|
|
425
451
|
StandardBusinessRoles,
|
|
426
452
|
createBusinessRole,
|
|
@@ -431,6 +457,9 @@ export {
|
|
|
431
457
|
findRoleForTask,
|
|
432
458
|
createTaskAssignment,
|
|
433
459
|
transitionTaskStatus,
|
|
460
|
+
// Conversion helpers
|
|
461
|
+
workerRefToAssignee,
|
|
462
|
+
assigneeToWorkerRef,
|
|
434
463
|
} from './roles.js'
|
|
435
464
|
|
|
436
465
|
// Export organization (full hierarchy to FGA/RBAC)
|
|
@@ -455,11 +484,7 @@ export type {
|
|
|
455
484
|
ResolvedPermissions,
|
|
456
485
|
} from './organization.js'
|
|
457
486
|
|
|
458
|
-
export {
|
|
459
|
-
resolvePermissions,
|
|
460
|
-
getApprovalChainForRequest,
|
|
461
|
-
findManager,
|
|
462
|
-
} from './organization.js'
|
|
487
|
+
export { resolvePermissions, getApprovalChainForRequest, findManager } from './organization.js'
|
|
463
488
|
|
|
464
489
|
// =============================================================================
|
|
465
490
|
// Entity Definitions (Noun pattern with Properties, Actions, Events)
|
|
@@ -528,3 +553,8 @@ export {
|
|
|
528
553
|
Entities,
|
|
529
554
|
type BusinessEntityCategory,
|
|
530
555
|
} from './entities/index.js'
|
|
556
|
+
|
|
557
|
+
// =============================================================================
|
|
558
|
+
// Business Model Canvas (TODO: implement canvas module)
|
|
559
|
+
// =============================================================================
|
|
560
|
+
// Canvas functionality is planned but not yet implemented
|
package/src/kpis.ts
CHANGED
|
@@ -1,8 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Key Performance Indicators (KPIs) management
|
|
3
|
+
*
|
|
4
|
+
* Uses org.ai KPI types for standardized KPI definitions across the ecosystem.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
import type { KPIDefinition, TimePeriod } from './types.js'
|
|
8
|
+
import type { KPI as OrgKPI, KPICategory, KPITrend, KPIFrequency, KPIHistoryEntry } from 'org.ai'
|
|
9
|
+
|
|
10
|
+
// Re-export org.ai KPI types for convenience
|
|
11
|
+
export type { OrgKPI, KPICategory, KPITrend, KPIFrequency, KPIHistoryEntry }
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Convert a business-as-code KPIDefinition to an org.ai KPI
|
|
15
|
+
*
|
|
16
|
+
* @param definition - Business KPI definition
|
|
17
|
+
* @param id - Unique identifier for the KPI
|
|
18
|
+
* @returns org.ai KPI object
|
|
19
|
+
*/
|
|
20
|
+
export function toOrgKPI(definition: KPIDefinition, id: string): OrgKPI {
|
|
21
|
+
const result: OrgKPI = {
|
|
22
|
+
id,
|
|
23
|
+
name: definition.name,
|
|
24
|
+
value: definition.current ?? 0,
|
|
25
|
+
target: definition.target ?? 0,
|
|
26
|
+
unit: definition.unit || '',
|
|
27
|
+
}
|
|
28
|
+
if (definition.description !== undefined) result.description = definition.description
|
|
29
|
+
if (definition.current !== undefined) result.current = definition.current
|
|
30
|
+
if (definition.category !== undefined) result.category = definition.category as KPICategory
|
|
31
|
+
if (definition.frequency !== undefined) result.frequency = definition.frequency as KPIFrequency
|
|
32
|
+
if (definition.dataSource !== undefined) result.dataSource = definition.dataSource
|
|
33
|
+
if (definition.formula !== undefined) result.formula = definition.formula
|
|
34
|
+
if (definition.metadata !== undefined) result.metadata = definition.metadata
|
|
35
|
+
return result
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Convert an org.ai KPI to a business-as-code KPIDefinition
|
|
40
|
+
*
|
|
41
|
+
* @param kpi - org.ai KPI object
|
|
42
|
+
* @returns Business KPI definition
|
|
43
|
+
*/
|
|
44
|
+
export function fromOrgKPI(kpi: OrgKPI): KPIDefinition {
|
|
45
|
+
const result: KPIDefinition = {
|
|
46
|
+
name: kpi.name,
|
|
47
|
+
unit: kpi.unit,
|
|
48
|
+
}
|
|
49
|
+
if (kpi.description !== undefined) result.description = kpi.description
|
|
50
|
+
const cat = kpi.category
|
|
51
|
+
if (cat !== undefined) result.category = cat as NonNullable<KPIDefinition['category']>
|
|
52
|
+
if (typeof kpi.target === 'number') result.target = kpi.target
|
|
53
|
+
if (typeof kpi.value === 'number') result.current = kpi.value
|
|
54
|
+
else if (typeof kpi.current === 'number') result.current = kpi.current
|
|
55
|
+
if (kpi.frequency !== undefined) result.frequency = kpi.frequency as TimePeriod
|
|
56
|
+
if (kpi.dataSource !== undefined) result.dataSource = kpi.dataSource
|
|
57
|
+
if (kpi.formula !== undefined) result.formula = kpi.formula
|
|
58
|
+
if (kpi.metadata !== undefined) result.metadata = kpi.metadata
|
|
59
|
+
return result
|
|
60
|
+
}
|
|
6
61
|
|
|
7
62
|
/**
|
|
8
63
|
* Define Key Performance Indicators for tracking business metrics
|
|
@@ -46,7 +101,7 @@ import type { KPIDefinition, TimePeriod } from './types.js'
|
|
|
46
101
|
* ```
|
|
47
102
|
*/
|
|
48
103
|
export function kpis(definitions: KPIDefinition[]): KPIDefinition[] {
|
|
49
|
-
return definitions.map(kpi => validateAndNormalizeKPI(kpi))
|
|
104
|
+
return definitions.map((kpi) => validateAndNormalizeKPI(kpi))
|
|
50
105
|
}
|
|
51
106
|
|
|
52
107
|
/**
|
|
@@ -88,7 +143,7 @@ export function meetsTarget(kpi: KPIDefinition): boolean {
|
|
|
88
143
|
if (kpi.target === undefined || kpi.current === undefined) return false
|
|
89
144
|
|
|
90
145
|
// For metrics where lower is better (like churn rate)
|
|
91
|
-
const lowerIsBetter = ['churn', 'cost', 'time', 'error', 'downtime'].some(term =>
|
|
146
|
+
const lowerIsBetter = ['churn', 'cost', 'time', 'error', 'downtime'].some((term) =>
|
|
92
147
|
kpi.name.toLowerCase().includes(term)
|
|
93
148
|
)
|
|
94
149
|
|
|
@@ -126,14 +181,14 @@ export function getKPIsByCategory(
|
|
|
126
181
|
kpis: KPIDefinition[],
|
|
127
182
|
category: KPIDefinition['category']
|
|
128
183
|
): KPIDefinition[] {
|
|
129
|
-
return kpis.filter(k => k.category === category)
|
|
184
|
+
return kpis.filter((k) => k.category === category)
|
|
130
185
|
}
|
|
131
186
|
|
|
132
187
|
/**
|
|
133
188
|
* Get KPIs by frequency
|
|
134
189
|
*/
|
|
135
190
|
export function getKPIsByFrequency(kpis: KPIDefinition[], frequency: TimePeriod): KPIDefinition[] {
|
|
136
|
-
return kpis.filter(k => k.frequency === frequency)
|
|
191
|
+
return kpis.filter((k) => k.frequency === frequency)
|
|
137
192
|
}
|
|
138
193
|
|
|
139
194
|
/**
|
|
@@ -147,7 +202,7 @@ export function getKPIsOnTarget(kpis: KPIDefinition[]): KPIDefinition[] {
|
|
|
147
202
|
* Get KPIs that don't meet their targets
|
|
148
203
|
*/
|
|
149
204
|
export function getKPIsOffTarget(kpis: KPIDefinition[]): KPIDefinition[] {
|
|
150
|
-
return kpis.filter(kpi => !meetsTarget(kpi))
|
|
205
|
+
return kpis.filter((kpi) => !meetsTarget(kpi))
|
|
151
206
|
}
|
|
152
207
|
|
|
153
208
|
/**
|
|
@@ -235,11 +290,10 @@ export function comparePerformance(
|
|
|
235
290
|
}
|
|
236
291
|
|
|
237
292
|
const change = current.current - previous.current
|
|
238
|
-
const changePercent =
|
|
239
|
-
previous.current !== 0 ? (change / previous.current) * 100 : 0
|
|
293
|
+
const changePercent = previous.current !== 0 ? (change / previous.current) * 100 : 0
|
|
240
294
|
|
|
241
295
|
// Determine if change is an improvement
|
|
242
|
-
const lowerIsBetter = ['churn', 'cost', 'time', 'error', 'downtime'].some(term =>
|
|
296
|
+
const lowerIsBetter = ['churn', 'cost', 'time', 'error', 'downtime'].some((term) =>
|
|
243
297
|
current.name.toLowerCase().includes(term)
|
|
244
298
|
)
|
|
245
299
|
|
package/src/metrics.ts
CHANGED
|
@@ -58,12 +58,12 @@ export interface TimeSeries<T = number> {
|
|
|
58
58
|
*/
|
|
59
59
|
export interface MRR {
|
|
60
60
|
total: number
|
|
61
|
-
newMRR: number
|
|
62
|
-
expansionMRR: number
|
|
63
|
-
contractionMRR: number
|
|
64
|
-
churnedMRR: number
|
|
65
|
-
reactivationMRR: number
|
|
66
|
-
netNewMRR: number
|
|
61
|
+
newMRR: number // From new customers
|
|
62
|
+
expansionMRR: number // From upgrades
|
|
63
|
+
contractionMRR: number // From downgrades
|
|
64
|
+
churnedMRR: number // From cancellations
|
|
65
|
+
reactivationMRR: number // From reactivations
|
|
66
|
+
netNewMRR: number // newMRR + expansionMRR - contractionMRR - churnedMRR + reactivationMRR
|
|
67
67
|
currency: Currency
|
|
68
68
|
period: MetricPeriod
|
|
69
69
|
}
|
|
@@ -73,8 +73,8 @@ export interface MRR {
|
|
|
73
73
|
*/
|
|
74
74
|
export interface ARR {
|
|
75
75
|
total: number
|
|
76
|
-
fromMRR?: number
|
|
77
|
-
contracted?: number
|
|
76
|
+
fromMRR?: number // MRR * 12
|
|
77
|
+
contracted?: number // From annual contracts
|
|
78
78
|
currency: Currency
|
|
79
79
|
asOf: Date
|
|
80
80
|
}
|
|
@@ -83,7 +83,7 @@ export interface ARR {
|
|
|
83
83
|
* Net Revenue Retention (NRR) / Dollar-based Net Retention (DBNR)
|
|
84
84
|
*/
|
|
85
85
|
export interface NRR {
|
|
86
|
-
rate: number
|
|
86
|
+
rate: number // Percentage (e.g., 115 = 115%)
|
|
87
87
|
startingMRR: number
|
|
88
88
|
endingMRR: number
|
|
89
89
|
expansion: number
|
|
@@ -96,7 +96,7 @@ export interface NRR {
|
|
|
96
96
|
* Gross Revenue Retention (GRR)
|
|
97
97
|
*/
|
|
98
98
|
export interface GRR {
|
|
99
|
-
rate: number
|
|
99
|
+
rate: number // Percentage (max 100%)
|
|
100
100
|
startingMRR: number
|
|
101
101
|
endingMRR: number
|
|
102
102
|
contraction: number
|
|
@@ -113,7 +113,7 @@ export interface ARPU {
|
|
|
113
113
|
totalUsers: number
|
|
114
114
|
currency: Currency
|
|
115
115
|
period: MetricPeriod
|
|
116
|
-
segment?: string
|
|
116
|
+
segment?: string // Optional segment (e.g., "enterprise", "smb")
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/**
|
|
@@ -125,7 +125,7 @@ export interface RevenueSegment {
|
|
|
125
125
|
arr: number
|
|
126
126
|
customers: number
|
|
127
127
|
arpu: number
|
|
128
|
-
growth: number
|
|
128
|
+
growth: number // MoM or YoY growth rate
|
|
129
129
|
currency: Currency
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -151,8 +151,8 @@ export interface CAC {
|
|
|
151
151
|
export interface LTV {
|
|
152
152
|
value: number
|
|
153
153
|
arpu: number
|
|
154
|
-
grossMargin: number
|
|
155
|
-
churnRate: number
|
|
154
|
+
grossMargin: number // Percentage
|
|
155
|
+
churnRate: number // Monthly churn rate
|
|
156
156
|
averageLifetimeMonths: number
|
|
157
157
|
currency: Currency
|
|
158
158
|
}
|
|
@@ -164,8 +164,8 @@ export interface LTVtoCAC {
|
|
|
164
164
|
ratio: number
|
|
165
165
|
ltv: number
|
|
166
166
|
cac: number
|
|
167
|
-
paybackMonths: number
|
|
168
|
-
healthy: boolean
|
|
167
|
+
paybackMonths: number // CAC / (ARPU * Gross Margin)
|
|
168
|
+
healthy: boolean // > 3 is generally healthy
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/**
|
|
@@ -173,12 +173,12 @@ export interface LTVtoCAC {
|
|
|
173
173
|
*/
|
|
174
174
|
export interface Churn {
|
|
175
175
|
// Customer churn (logo churn)
|
|
176
|
-
customerChurnRate: number
|
|
176
|
+
customerChurnRate: number // Percentage
|
|
177
177
|
customersLost: number
|
|
178
178
|
customersStart: number
|
|
179
179
|
|
|
180
180
|
// Revenue churn
|
|
181
|
-
revenueChurnRate: number
|
|
181
|
+
revenueChurnRate: number // Percentage (gross churn)
|
|
182
182
|
mrrChurned: number
|
|
183
183
|
|
|
184
184
|
// Net revenue churn (can be negative with good expansion)
|
|
@@ -192,11 +192,11 @@ export interface Churn {
|
|
|
192
192
|
*/
|
|
193
193
|
export interface RetentionCohort {
|
|
194
194
|
cohortDate: Date
|
|
195
|
-
cohortLabel: string
|
|
195
|
+
cohortLabel: string // e.g., "Jan 2024"
|
|
196
196
|
initialCustomers: number
|
|
197
197
|
initialMRR: number
|
|
198
|
-
retentionByMonth: number[]
|
|
199
|
-
revenueByMonth: number[]
|
|
198
|
+
retentionByMonth: number[] // Array of retention rates by month
|
|
199
|
+
revenueByMonth: number[] // Array of MRR by month
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
// =============================================================================
|
|
@@ -207,11 +207,11 @@ export interface RetentionCohort {
|
|
|
207
207
|
* Growth rate metrics
|
|
208
208
|
*/
|
|
209
209
|
export interface GrowthRate {
|
|
210
|
-
mom: number
|
|
211
|
-
qoq: number
|
|
212
|
-
yoy: number
|
|
213
|
-
cagr?: number
|
|
214
|
-
metric: string
|
|
210
|
+
mom: number // Month-over-month
|
|
211
|
+
qoq: number // Quarter-over-quarter
|
|
212
|
+
yoy: number // Year-over-year
|
|
213
|
+
cagr?: number // Compound annual growth rate
|
|
214
|
+
metric: string // What metric this growth rate is for
|
|
215
215
|
period: MetricPeriod
|
|
216
216
|
}
|
|
217
217
|
|
|
@@ -225,7 +225,7 @@ export interface QuickRatio {
|
|
|
225
225
|
expansionMRR: number
|
|
226
226
|
churnedMRR: number
|
|
227
227
|
contractionMRR: number
|
|
228
|
-
healthy: boolean
|
|
228
|
+
healthy: boolean // > 4 is good, > 1 means growing
|
|
229
229
|
period: MetricPeriod
|
|
230
230
|
}
|
|
231
231
|
|
|
@@ -241,7 +241,7 @@ export interface MagicNumber {
|
|
|
241
241
|
value: number
|
|
242
242
|
netNewARR: number
|
|
243
243
|
salesMarketingSpend: number
|
|
244
|
-
efficient: boolean
|
|
244
|
+
efficient: boolean // > 0.75 is efficient
|
|
245
245
|
period: MetricPeriod
|
|
246
246
|
}
|
|
247
247
|
|
|
@@ -253,7 +253,7 @@ export interface BurnMultiple {
|
|
|
253
253
|
value: number
|
|
254
254
|
netBurn: number
|
|
255
255
|
netNewARR: number
|
|
256
|
-
efficient: boolean
|
|
256
|
+
efficient: boolean // < 1.5 is good
|
|
257
257
|
period: MetricPeriod
|
|
258
258
|
}
|
|
259
259
|
|
|
@@ -264,8 +264,8 @@ export interface BurnMultiple {
|
|
|
264
264
|
export interface RuleOf40 {
|
|
265
265
|
score: number
|
|
266
266
|
revenueGrowthRate: number
|
|
267
|
-
profitMargin: number
|
|
268
|
-
passing: boolean
|
|
267
|
+
profitMargin: number // Or EBITDA margin
|
|
268
|
+
passing: boolean // >= 40 is passing
|
|
269
269
|
period: MetricPeriod
|
|
270
270
|
}
|
|
271
271
|
|
|
@@ -274,7 +274,7 @@ export interface RuleOf40 {
|
|
|
274
274
|
* Combines multiple efficiency metrics
|
|
275
275
|
*/
|
|
276
276
|
export interface EfficiencyScore {
|
|
277
|
-
overall: number
|
|
277
|
+
overall: number // 0-100 score
|
|
278
278
|
components: {
|
|
279
279
|
ltvCacRatio: number
|
|
280
280
|
magicNumber: number
|
|
@@ -294,10 +294,10 @@ export interface EfficiencyScore {
|
|
|
294
294
|
*/
|
|
295
295
|
export interface Pipeline {
|
|
296
296
|
totalValue: number
|
|
297
|
-
weightedValue: number
|
|
297
|
+
weightedValue: number // Probability-adjusted
|
|
298
298
|
stages: PipelineStage[]
|
|
299
|
-
velocity: number
|
|
300
|
-
conversionRate: number
|
|
299
|
+
velocity: number // Average days to close
|
|
300
|
+
conversionRate: number // Win rate
|
|
301
301
|
currency: Currency
|
|
302
302
|
asOf: Date
|
|
303
303
|
}
|
|
@@ -318,7 +318,7 @@ export interface PipelineStage {
|
|
|
318
318
|
* (Opportunities * Win Rate * Average Deal Size) / Sales Cycle Length
|
|
319
319
|
*/
|
|
320
320
|
export interface SalesVelocity {
|
|
321
|
-
value: number
|
|
321
|
+
value: number // Revenue per day
|
|
322
322
|
opportunities: number
|
|
323
323
|
winRate: number
|
|
324
324
|
averageDealSize: number
|
|
@@ -335,10 +335,10 @@ export interface SalesVelocity {
|
|
|
335
335
|
* Net Promoter Score
|
|
336
336
|
*/
|
|
337
337
|
export interface NPS {
|
|
338
|
-
score: number
|
|
339
|
-
promoters: number
|
|
340
|
-
passives: number
|
|
341
|
-
detractors: number
|
|
338
|
+
score: number // -100 to 100
|
|
339
|
+
promoters: number // 9-10
|
|
340
|
+
passives: number // 7-8
|
|
341
|
+
detractors: number // 0-6
|
|
342
342
|
responses: number
|
|
343
343
|
responseRate?: number
|
|
344
344
|
asOf: Date
|
|
@@ -348,10 +348,10 @@ export interface NPS {
|
|
|
348
348
|
* Customer health score
|
|
349
349
|
*/
|
|
350
350
|
export interface CustomerHealth {
|
|
351
|
-
averageScore: number
|
|
352
|
-
healthy: number
|
|
353
|
-
atRisk: number
|
|
354
|
-
critical: number
|
|
351
|
+
averageScore: number // 0-100
|
|
352
|
+
healthy: number // Count
|
|
353
|
+
atRisk: number // Count
|
|
354
|
+
critical: number // Count
|
|
355
355
|
factors: HealthFactor[]
|
|
356
356
|
asOf: Date
|
|
357
357
|
}
|
|
@@ -422,7 +422,8 @@ export function calculateMRR(input: {
|
|
|
422
422
|
period: MetricPeriod
|
|
423
423
|
}): MRR {
|
|
424
424
|
const reactivationMRR = input.reactivationMRR || 0
|
|
425
|
-
const netNewMRR =
|
|
425
|
+
const netNewMRR =
|
|
426
|
+
input.newMRR + input.expansionMRR - input.contractionMRR - input.churnedMRR + reactivationMRR
|
|
426
427
|
const total = input.previousMRR + netNewMRR
|
|
427
428
|
|
|
428
429
|
return {
|
|
@@ -508,22 +509,23 @@ export function calculateCACMetric(input: {
|
|
|
508
509
|
}): CAC {
|
|
509
510
|
const value = input.newCustomers > 0 ? input.salesMarketingSpend / input.newCustomers : 0
|
|
510
511
|
|
|
511
|
-
|
|
512
|
-
if (input.byChannel) {
|
|
513
|
-
byChannel = {}
|
|
514
|
-
for (const [channel, data] of Object.entries(input.byChannel)) {
|
|
515
|
-
byChannel[channel] = data.customers > 0 ? data.spend / data.customers : 0
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
return {
|
|
512
|
+
const result: CAC = {
|
|
520
513
|
value,
|
|
521
514
|
totalSalesMarketingSpend: input.salesMarketingSpend,
|
|
522
515
|
newCustomersAcquired: input.newCustomers,
|
|
523
516
|
currency: input.currency || 'USD',
|
|
524
517
|
period: input.period,
|
|
525
|
-
byChannel,
|
|
526
518
|
}
|
|
519
|
+
|
|
520
|
+
if (input.byChannel) {
|
|
521
|
+
const byChannel: Record<string, number> = {}
|
|
522
|
+
for (const [channel, data] of Object.entries(input.byChannel)) {
|
|
523
|
+
byChannel[channel] = data.customers > 0 ? data.spend / data.customers : 0
|
|
524
|
+
}
|
|
525
|
+
result.byChannel = byChannel
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return result
|
|
527
529
|
}
|
|
528
530
|
|
|
529
531
|
/**
|
|
@@ -537,7 +539,7 @@ export function calculateLTVMetric(input: {
|
|
|
537
539
|
}): LTV {
|
|
538
540
|
// LTV = (ARPU * Gross Margin) / Churn Rate
|
|
539
541
|
const averageLifetimeMonths = input.churnRate > 0 ? 1 / input.churnRate : 0
|
|
540
|
-
const value = input.churnRate > 0 ? (input.arpu * input.grossMargin / 100
|
|
542
|
+
const value = input.churnRate > 0 ? (input.arpu * input.grossMargin) / 100 / input.churnRate : 0
|
|
541
543
|
|
|
542
544
|
return {
|
|
543
545
|
value,
|
|
@@ -554,9 +556,8 @@ export function calculateLTVMetric(input: {
|
|
|
554
556
|
*/
|
|
555
557
|
export function calculateLTVtoCACRatio(ltv: LTV, cac: CAC): LTVtoCAC {
|
|
556
558
|
const ratio = cac.value > 0 ? ltv.value / cac.value : 0
|
|
557
|
-
const paybackMonths =
|
|
558
|
-
? cac.value / (ltv.arpu * ltv.grossMargin / 100)
|
|
559
|
-
: 0
|
|
559
|
+
const paybackMonths =
|
|
560
|
+
ltv.arpu > 0 && ltv.grossMargin > 0 ? cac.value / ((ltv.arpu * ltv.grossMargin) / 100) : 0
|
|
560
561
|
|
|
561
562
|
return {
|
|
562
563
|
ratio,
|
|
@@ -654,15 +655,18 @@ export function calculateGrowthRates(input: {
|
|
|
654
655
|
metric: string
|
|
655
656
|
period: MetricPeriod
|
|
656
657
|
}): GrowthRate {
|
|
657
|
-
const mom =
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
658
|
+
const mom =
|
|
659
|
+
input.previousMonth && input.previousMonth > 0
|
|
660
|
+
? ((input.current - input.previousMonth) / input.previousMonth) * 100
|
|
661
|
+
: 0
|
|
662
|
+
const qoq =
|
|
663
|
+
input.previousQuarter && input.previousQuarter > 0
|
|
664
|
+
? ((input.current - input.previousQuarter) / input.previousQuarter) * 100
|
|
665
|
+
: 0
|
|
666
|
+
const yoy =
|
|
667
|
+
input.previousYear && input.previousYear > 0
|
|
668
|
+
? ((input.current - input.previousYear) / input.previousYear) * 100
|
|
669
|
+
: 0
|
|
666
670
|
|
|
667
671
|
return {
|
|
668
672
|
mom,
|
|
@@ -684,15 +688,11 @@ export function calculateChurnMetrics(input: {
|
|
|
684
688
|
expansionMRR: number
|
|
685
689
|
period: MetricPeriod
|
|
686
690
|
}): Churn {
|
|
687
|
-
const customerChurnRate =
|
|
688
|
-
? (input.customersLost / input.customersStart) * 100
|
|
689
|
-
|
|
690
|
-
const
|
|
691
|
-
? (input.mrrChurned / input.mrrStart) * 100
|
|
692
|
-
: 0
|
|
693
|
-
const netRevenueChurnRate = input.mrrStart > 0
|
|
694
|
-
? ((input.mrrChurned - input.expansionMRR) / input.mrrStart) * 100
|
|
695
|
-
: 0
|
|
691
|
+
const customerChurnRate =
|
|
692
|
+
input.customersStart > 0 ? (input.customersLost / input.customersStart) * 100 : 0
|
|
693
|
+
const revenueChurnRate = input.mrrStart > 0 ? (input.mrrChurned / input.mrrStart) * 100 : 0
|
|
694
|
+
const netRevenueChurnRate =
|
|
695
|
+
input.mrrStart > 0 ? ((input.mrrChurned - input.expansionMRR) / input.mrrStart) * 100 : 0
|
|
696
696
|
|
|
697
697
|
return {
|
|
698
698
|
customerChurnRate,
|
|
@@ -728,7 +728,7 @@ export function aggregateTimeSeries<T extends number>(
|
|
|
728
728
|
const aggregation = series.aggregation || 'sum'
|
|
729
729
|
|
|
730
730
|
for (const [key, points] of buckets) {
|
|
731
|
-
const values = points.map(p => p.value as number)
|
|
731
|
+
const values = points.map((p) => p.value as number)
|
|
732
732
|
let aggregatedValue: number
|
|
733
733
|
|
|
734
734
|
switch (aggregation) {
|
|
@@ -809,7 +809,20 @@ export function createMetricPeriod(
|
|
|
809
809
|
* Format period label
|
|
810
810
|
*/
|
|
811
811
|
function formatPeriodLabel(period: TimePeriod, start: Date, end: Date): string {
|
|
812
|
-
const monthNames = [
|
|
812
|
+
const monthNames = [
|
|
813
|
+
'Jan',
|
|
814
|
+
'Feb',
|
|
815
|
+
'Mar',
|
|
816
|
+
'Apr',
|
|
817
|
+
'May',
|
|
818
|
+
'Jun',
|
|
819
|
+
'Jul',
|
|
820
|
+
'Aug',
|
|
821
|
+
'Sep',
|
|
822
|
+
'Oct',
|
|
823
|
+
'Nov',
|
|
824
|
+
'Dec',
|
|
825
|
+
]
|
|
813
826
|
|
|
814
827
|
switch (period) {
|
|
815
828
|
case 'monthly':
|