business-as-code 2.1.3 → 2.3.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.
Files changed (260) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +2 -0
  3. package/package.json +16 -13
  4. package/src/dollar.ts +5 -2
  5. package/src/entities/organization.ts +31 -18
  6. package/src/goals.ts +78 -12
  7. package/src/index.ts +48 -18
  8. package/src/kpis.ts +62 -8
  9. package/src/metrics.ts +92 -79
  10. package/src/okrs.ts +120 -20
  11. package/src/organization.ts +12 -15
  12. package/src/process.ts +11 -12
  13. package/src/product.ts +8 -9
  14. package/src/queries.ts +238 -75
  15. package/src/roles.ts +62 -61
  16. package/src/workflow.ts +22 -15
  17. package/test/business.test.ts +282 -0
  18. package/test/dollar.test.ts +270 -0
  19. package/test/entities.test.ts +628 -0
  20. package/test/financials.test.ts +539 -0
  21. package/test/goals.test.ts +451 -0
  22. package/{src → test}/index.test.ts +1 -1
  23. package/test/kpis.test.ts +440 -0
  24. package/test/metrics.test.ts +744 -0
  25. package/test/okrs.test.ts +741 -0
  26. package/test/organization.test.ts +548 -0
  27. package/test/process.test.ts +503 -0
  28. package/test/product.test.ts +430 -0
  29. package/test/queries.test.ts +556 -0
  30. package/test/roles.test.ts +546 -0
  31. package/test/service.test.ts +450 -0
  32. package/test/types.test.ts +1141 -0
  33. package/test/vision.test.ts +214 -0
  34. package/test/workflow.test.ts +501 -0
  35. package/vitest.config.ts +47 -0
  36. package/.turbo/turbo-build.log +0 -5
  37. package/LICENSE +0 -21
  38. package/dist/business.d.ts +0 -62
  39. package/dist/business.d.ts.map +0 -1
  40. package/dist/business.js +0 -109
  41. package/dist/business.js.map +0 -1
  42. package/dist/canvas/activities.d.ts +0 -19
  43. package/dist/canvas/activities.d.ts.map +0 -1
  44. package/dist/canvas/activities.js +0 -20
  45. package/dist/canvas/activities.js.map +0 -1
  46. package/dist/canvas/channels.d.ts +0 -20
  47. package/dist/canvas/channels.d.ts.map +0 -1
  48. package/dist/canvas/channels.js +0 -21
  49. package/dist/canvas/channels.js.map +0 -1
  50. package/dist/canvas/relationships.d.ts +0 -20
  51. package/dist/canvas/relationships.d.ts.map +0 -1
  52. package/dist/canvas/relationships.js +0 -21
  53. package/dist/canvas/relationships.js.map +0 -1
  54. package/dist/canvas/resources.d.ts +0 -20
  55. package/dist/canvas/resources.d.ts.map +0 -1
  56. package/dist/canvas/resources.js +0 -30
  57. package/dist/canvas/resources.js.map +0 -1
  58. package/dist/canvas/revenue.d.ts +0 -22
  59. package/dist/canvas/revenue.d.ts.map +0 -1
  60. package/dist/canvas/revenue.js +0 -30
  61. package/dist/canvas/revenue.js.map +0 -1
  62. package/dist/canvas/segments.d.ts +0 -20
  63. package/dist/canvas/segments.d.ts.map +0 -1
  64. package/dist/canvas/segments.js +0 -28
  65. package/dist/canvas/segments.js.map +0 -1
  66. package/dist/canvas/types.d.ts +0 -232
  67. package/dist/canvas/types.d.ts.map +0 -1
  68. package/dist/canvas/types.js +0 -8
  69. package/dist/canvas/types.js.map +0 -1
  70. package/dist/canvas/value.d.ts +0 -20
  71. package/dist/canvas/value.d.ts.map +0 -1
  72. package/dist/canvas/value.js +0 -21
  73. package/dist/canvas/value.js.map +0 -1
  74. package/dist/dollar.d.ts +0 -60
  75. package/dist/dollar.d.ts.map +0 -1
  76. package/dist/dollar.js +0 -107
  77. package/dist/dollar.js.map +0 -1
  78. package/dist/entities/assets.d.ts +0 -21
  79. package/dist/entities/assets.d.ts.map +0 -1
  80. package/dist/entities/assets.js +0 -323
  81. package/dist/entities/assets.js.map +0 -1
  82. package/dist/entities/business.d.ts +0 -36
  83. package/dist/entities/business.d.ts.map +0 -1
  84. package/dist/entities/business.js +0 -370
  85. package/dist/entities/business.js.map +0 -1
  86. package/dist/entities/communication.d.ts +0 -21
  87. package/dist/entities/communication.d.ts.map +0 -1
  88. package/dist/entities/communication.js +0 -255
  89. package/dist/entities/communication.js.map +0 -1
  90. package/dist/entities/customers.d.ts +0 -58
  91. package/dist/entities/customers.d.ts.map +0 -1
  92. package/dist/entities/customers.js +0 -989
  93. package/dist/entities/customers.js.map +0 -1
  94. package/dist/entities/financials.d.ts +0 -59
  95. package/dist/entities/financials.d.ts.map +0 -1
  96. package/dist/entities/financials.js +0 -932
  97. package/dist/entities/financials.js.map +0 -1
  98. package/dist/entities/goals.d.ts +0 -58
  99. package/dist/entities/goals.d.ts.map +0 -1
  100. package/dist/entities/goals.js +0 -800
  101. package/dist/entities/goals.js.map +0 -1
  102. package/dist/entities/index.d.ts +0 -299
  103. package/dist/entities/index.d.ts.map +0 -1
  104. package/dist/entities/index.js +0 -198
  105. package/dist/entities/index.js.map +0 -1
  106. package/dist/entities/legal.d.ts +0 -21
  107. package/dist/entities/legal.d.ts.map +0 -1
  108. package/dist/entities/legal.js +0 -301
  109. package/dist/entities/legal.js.map +0 -1
  110. package/dist/entities/market.d.ts +0 -21
  111. package/dist/entities/market.d.ts.map +0 -1
  112. package/dist/entities/market.js +0 -301
  113. package/dist/entities/market.js.map +0 -1
  114. package/dist/entities/marketing.d.ts +0 -67
  115. package/dist/entities/marketing.d.ts.map +0 -1
  116. package/dist/entities/marketing.js +0 -1157
  117. package/dist/entities/marketing.js.map +0 -1
  118. package/dist/entities/offerings.d.ts +0 -51
  119. package/dist/entities/offerings.d.ts.map +0 -1
  120. package/dist/entities/offerings.js +0 -727
  121. package/dist/entities/offerings.js.map +0 -1
  122. package/dist/entities/operations.d.ts +0 -58
  123. package/dist/entities/operations.d.ts.map +0 -1
  124. package/dist/entities/operations.js +0 -787
  125. package/dist/entities/operations.js.map +0 -1
  126. package/dist/entities/organization.d.ts +0 -57
  127. package/dist/entities/organization.d.ts.map +0 -1
  128. package/dist/entities/organization.js +0 -807
  129. package/dist/entities/organization.js.map +0 -1
  130. package/dist/entities/partnerships.d.ts +0 -21
  131. package/dist/entities/partnerships.d.ts.map +0 -1
  132. package/dist/entities/partnerships.js +0 -300
  133. package/dist/entities/partnerships.js.map +0 -1
  134. package/dist/entities/planning.d.ts +0 -0
  135. package/dist/entities/planning.d.ts.map +0 -1
  136. package/dist/entities/planning.js +0 -271
  137. package/dist/entities/planning.js.map +0 -1
  138. package/dist/entities/projects.d.ts +0 -25
  139. package/dist/entities/projects.d.ts.map +0 -1
  140. package/dist/entities/projects.js +0 -349
  141. package/dist/entities/projects.js.map +0 -1
  142. package/dist/entities/risk.d.ts +0 -21
  143. package/dist/entities/risk.d.ts.map +0 -1
  144. package/dist/entities/risk.js +0 -293
  145. package/dist/entities/risk.js.map +0 -1
  146. package/dist/entities/sales.d.ts +0 -72
  147. package/dist/entities/sales.d.ts.map +0 -1
  148. package/dist/entities/sales.js +0 -1248
  149. package/dist/entities/sales.js.map +0 -1
  150. package/dist/financials.d.ts +0 -130
  151. package/dist/financials.d.ts.map +0 -1
  152. package/dist/financials.js +0 -297
  153. package/dist/financials.js.map +0 -1
  154. package/dist/goals.d.ts +0 -87
  155. package/dist/goals.d.ts.map +0 -1
  156. package/dist/goals.js +0 -215
  157. package/dist/goals.js.map +0 -1
  158. package/dist/index.d.ts +0 -97
  159. package/dist/index.d.ts.map +0 -1
  160. package/dist/index.js +0 -132
  161. package/dist/index.js.map +0 -1
  162. package/dist/kpis.d.ts +0 -118
  163. package/dist/kpis.d.ts.map +0 -1
  164. package/dist/kpis.js +0 -232
  165. package/dist/kpis.js.map +0 -1
  166. package/dist/metrics.d.ts +0 -448
  167. package/dist/metrics.d.ts.map +0 -1
  168. package/dist/metrics.js +0 -325
  169. package/dist/metrics.js.map +0 -1
  170. package/dist/okrs.d.ts +0 -123
  171. package/dist/okrs.d.ts.map +0 -1
  172. package/dist/okrs.js +0 -269
  173. package/dist/okrs.js.map +0 -1
  174. package/dist/organization.d.ts +0 -585
  175. package/dist/organization.d.ts.map +0 -1
  176. package/dist/organization.js +0 -173
  177. package/dist/organization.js.map +0 -1
  178. package/dist/process.d.ts +0 -112
  179. package/dist/process.d.ts.map +0 -1
  180. package/dist/process.js +0 -241
  181. package/dist/process.js.map +0 -1
  182. package/dist/product.d.ts +0 -85
  183. package/dist/product.d.ts.map +0 -1
  184. package/dist/product.js +0 -145
  185. package/dist/product.js.map +0 -1
  186. package/dist/queries.d.ts +0 -304
  187. package/dist/queries.d.ts.map +0 -1
  188. package/dist/queries.js +0 -415
  189. package/dist/queries.js.map +0 -1
  190. package/dist/roles.d.ts +0 -340
  191. package/dist/roles.d.ts.map +0 -1
  192. package/dist/roles.js +0 -255
  193. package/dist/roles.js.map +0 -1
  194. package/dist/service.d.ts +0 -61
  195. package/dist/service.d.ts.map +0 -1
  196. package/dist/service.js +0 -140
  197. package/dist/service.js.map +0 -1
  198. package/dist/types.d.ts +0 -459
  199. package/dist/types.d.ts.map +0 -1
  200. package/dist/types.js +0 -5
  201. package/dist/types.js.map +0 -1
  202. package/dist/vision.d.ts +0 -38
  203. package/dist/vision.d.ts.map +0 -1
  204. package/dist/vision.js +0 -68
  205. package/dist/vision.js.map +0 -1
  206. package/dist/workflow.d.ts +0 -115
  207. package/dist/workflow.d.ts.map +0 -1
  208. package/dist/workflow.js +0 -247
  209. package/dist/workflow.js.map +0 -1
  210. package/src/business.js +0 -108
  211. package/src/canvas/activities.ts +0 -32
  212. package/src/canvas/canvas.ts +0 -482
  213. package/src/canvas/channels.ts +0 -34
  214. package/src/canvas/costs.ts +0 -43
  215. package/src/canvas/economics.ts +0 -99
  216. package/src/canvas/index.ts +0 -206
  217. package/src/canvas/partnerships.ts +0 -34
  218. package/src/canvas/projections.ts +0 -141
  219. package/src/canvas/relationships.ts +0 -34
  220. package/src/canvas/resources.ts +0 -43
  221. package/src/canvas/revenue.ts +0 -56
  222. package/src/canvas/segments.ts +0 -42
  223. package/src/canvas/types.ts +0 -363
  224. package/src/canvas/value.ts +0 -34
  225. package/src/dollar.js +0 -106
  226. package/src/entities/assets.js +0 -322
  227. package/src/entities/business.js +0 -369
  228. package/src/entities/communication.js +0 -254
  229. package/src/entities/customers.js +0 -988
  230. package/src/entities/financials.js +0 -931
  231. package/src/entities/goals.js +0 -799
  232. package/src/entities/index.js +0 -197
  233. package/src/entities/legal.js +0 -300
  234. package/src/entities/market.js +0 -300
  235. package/src/entities/marketing.js +0 -1156
  236. package/src/entities/offerings.js +0 -726
  237. package/src/entities/operations.js +0 -786
  238. package/src/entities/organization.js +0 -806
  239. package/src/entities/partnerships.js +0 -299
  240. package/src/entities/planning.js +0 -270
  241. package/src/entities/projects.js +0 -348
  242. package/src/entities/risk.js +0 -292
  243. package/src/entities/sales.js +0 -1247
  244. package/src/financials.js +0 -296
  245. package/src/goals.js +0 -214
  246. package/src/index.js +0 -131
  247. package/src/index.test.js +0 -274
  248. package/src/kpis.js +0 -231
  249. package/src/metrics.js +0 -324
  250. package/src/okrs.js +0 -268
  251. package/src/organization.js +0 -172
  252. package/src/process.js +0 -240
  253. package/src/product.js +0 -144
  254. package/src/queries.js +0 -414
  255. package/src/roles.js +0 -254
  256. package/src/service.js +0 -139
  257. package/src/types.js +0 -4
  258. package/src/vision.js +0 -67
  259. package/src/workflow.js +0 -246
  260. package/tests/canvas.test.ts +0 -842
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 // 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
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 // MRR * 12
77
- contracted?: number // From annual contracts
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 // Percentage (e.g., 115 = 115%)
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 // Percentage (max 100%)
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 // Optional segment (e.g., "enterprise", "smb")
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 // MoM or YoY growth rate
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 // Percentage
155
- churnRate: number // Monthly churn rate
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 // CAC / (ARPU * Gross Margin)
168
- healthy: boolean // > 3 is generally healthy
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 // Percentage
176
+ customerChurnRate: number // Percentage
177
177
  customersLost: number
178
178
  customersStart: number
179
179
 
180
180
  // Revenue churn
181
- revenueChurnRate: number // Percentage (gross churn)
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 // e.g., "Jan 2024"
195
+ cohortLabel: string // e.g., "Jan 2024"
196
196
  initialCustomers: number
197
197
  initialMRR: number
198
- retentionByMonth: number[] // Array of retention rates by month
199
- revenueByMonth: number[] // Array of MRR by month
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 // 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
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 // > 4 is good, > 1 means growing
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 // > 0.75 is efficient
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 // < 1.5 is good
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 // Or EBITDA margin
268
- passing: boolean // >= 40 is passing
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 // 0-100 score
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 // Probability-adjusted
297
+ weightedValue: number // Probability-adjusted
298
298
  stages: PipelineStage[]
299
- velocity: number // Average days to close
300
- conversionRate: number // Win rate
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 // Revenue per day
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 // -100 to 100
339
- promoters: number // 9-10
340
- passives: number // 7-8
341
- detractors: number // 0-6
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 // 0-100
352
- healthy: number // Count
353
- atRisk: number // Count
354
- critical: number // Count
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 = input.newMRR + input.expansionMRR - input.contractionMRR - input.churnedMRR + reactivationMRR
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
- let byChannel: Record<string, number> | undefined
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) / input.churnRate : 0
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 = ltv.arpu > 0 && ltv.grossMargin > 0
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 = input.previousMonth && input.previousMonth > 0
658
- ? ((input.current - input.previousMonth) / input.previousMonth) * 100
659
- : 0
660
- const qoq = input.previousQuarter && input.previousQuarter > 0
661
- ? ((input.current - input.previousQuarter) / input.previousQuarter) * 100
662
- : 0
663
- const yoy = input.previousYear && input.previousYear > 0
664
- ? ((input.current - input.previousYear) / input.previousYear) * 100
665
- : 0
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 = input.customersStart > 0
688
- ? (input.customersLost / input.customersStart) * 100
689
- : 0
690
- const revenueChurnRate = input.mrrStart > 0
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 = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
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':
package/src/okrs.ts CHANGED
@@ -1,8 +1,106 @@
1
1
  /**
2
2
  * Objectives and Key Results (OKRs) management
3
+ *
4
+ * Uses org.ai OKR types for standardized OKR definitions across the ecosystem.
3
5
  */
4
6
 
5
7
  import type { OKRDefinition, KeyResult } from './types.js'
8
+ import type { OKR as OrgOKR, KeyResult as OrgKeyResult, OKRStatus, KeyResultStatus } from 'org.ai'
9
+
10
+ // Re-export org.ai OKR types for convenience
11
+ export type { OrgOKR, OrgKeyResult, OKRStatus, KeyResultStatus }
12
+
13
+ /**
14
+ * Convert a business-as-code KeyResult to an org.ai KeyResult
15
+ *
16
+ * @param kr - Business key result
17
+ * @param id - Optional identifier
18
+ * @returns org.ai KeyResult object
19
+ */
20
+ export function toOrgKeyResult(kr: KeyResult, id?: string): OrgKeyResult {
21
+ const result: OrgKeyResult = {
22
+ description: kr.description,
23
+ }
24
+ if (id !== undefined) result.id = id
25
+ if (kr.description) result.name = kr.description
26
+ if (kr.metric !== undefined) result.metric = kr.metric
27
+ if (kr.startValue !== undefined) result.startValue = kr.startValue
28
+ if (kr.targetValue !== undefined) {
29
+ result.targetValue = kr.targetValue
30
+ result.target = kr.targetValue
31
+ }
32
+ if (kr.currentValue !== undefined) {
33
+ result.currentValue = kr.currentValue
34
+ result.current = kr.currentValue
35
+ }
36
+ if (kr.unit !== undefined) result.unit = kr.unit
37
+ if (kr.progress !== undefined) result.progress = kr.progress
38
+ return result
39
+ }
40
+
41
+ /**
42
+ * Convert an org.ai KeyResult to a business-as-code KeyResult
43
+ *
44
+ * @param kr - org.ai KeyResult object
45
+ * @returns Business key result
46
+ */
47
+ export function fromOrgKeyResult(kr: OrgKeyResult): KeyResult {
48
+ const result: KeyResult = {
49
+ description: kr.description || kr.name || '',
50
+ metric: kr.metric || '',
51
+ targetValue: kr.targetValue ?? kr.target ?? 0,
52
+ }
53
+ if (kr.startValue !== undefined) result.startValue = kr.startValue
54
+ if (kr.currentValue !== undefined) result.currentValue = kr.currentValue
55
+ else if (kr.current !== undefined) result.currentValue = kr.current
56
+ if (kr.unit !== undefined) result.unit = kr.unit
57
+ if (kr.progress !== undefined) result.progress = kr.progress
58
+ return result
59
+ }
60
+
61
+ /**
62
+ * Convert a business-as-code OKRDefinition to an org.ai OKR
63
+ *
64
+ * @param definition - Business OKR definition
65
+ * @param id - Optional unique identifier
66
+ * @returns org.ai OKR object
67
+ */
68
+ export function toOrgOKR(definition: OKRDefinition, id?: string): OrgOKR {
69
+ const result: OrgOKR = {
70
+ objective: definition.objective,
71
+ keyResults: definition.keyResults?.map((kr, i) => toOrgKeyResult(kr, `${id}_kr_${i}`)) || [],
72
+ }
73
+ if (id !== undefined) result.id = id
74
+ if (definition.description !== undefined) result.description = definition.description
75
+ if (definition.owner !== undefined) result.owner = definition.owner
76
+ if (definition.period !== undefined) result.period = definition.period
77
+ if (definition.status !== undefined) result.status = definition.status as OKRStatus
78
+ result.progress = calculateOKRProgress(definition)
79
+ if (definition.confidence !== undefined) result.confidence = definition.confidence
80
+ if (definition.metadata !== undefined) result.metadata = definition.metadata
81
+ return result
82
+ }
83
+
84
+ /**
85
+ * Convert an org.ai OKR to a business-as-code OKRDefinition
86
+ *
87
+ * @param okr - org.ai OKR object
88
+ * @returns Business OKR definition
89
+ */
90
+ export function fromOrgOKR(okr: OrgOKR): OKRDefinition {
91
+ const result: OKRDefinition = {
92
+ objective: okr.objective,
93
+ keyResults: okr.keyResults.map(fromOrgKeyResult),
94
+ }
95
+ if (okr.description !== undefined) result.description = okr.description
96
+ if (okr.owner !== undefined) result.owner = okr.owner
97
+ if (okr.period !== undefined) result.period = okr.period
98
+ const st = okr.status
99
+ if (st !== undefined) result.status = st as NonNullable<OKRDefinition['status']>
100
+ if (okr.confidence !== undefined) result.confidence = okr.confidence
101
+ if (okr.metadata !== undefined) result.metadata = okr.metadata
102
+ return result
103
+ }
6
104
 
7
105
  /**
8
106
  * Define Objectives and Key Results for goal tracking
@@ -51,7 +149,7 @@ import type { OKRDefinition, KeyResult } from './types.js'
51
149
  * ```
52
150
  */
53
151
  export function okrs(definitions: OKRDefinition[]): OKRDefinition[] {
54
- return definitions.map(okr => validateAndNormalizeOKR(okr))
152
+ return definitions.map((okr) => validateAndNormalizeOKR(okr))
55
153
  }
56
154
 
57
155
  /**
@@ -70,18 +168,19 @@ function validateAndNormalizeOKR(okr: OKRDefinition): OKRDefinition {
70
168
  }
71
169
 
72
170
  // Calculate progress for key results if not set
73
- const keyResults = okr.keyResults?.map(kr => ({
171
+ const keyResults = okr.keyResults?.map((kr) => ({
74
172
  ...kr,
75
173
  progress: kr.progress ?? calculateKeyResultProgress(kr),
76
174
  }))
77
175
 
78
- return {
176
+ const result: OKRDefinition = {
79
177
  ...okr,
80
- keyResults,
81
178
  status: okr.status || 'not-started',
82
179
  confidence: okr.confidence ?? calculateConfidence(keyResults || []),
83
180
  metadata: okr.metadata || {},
84
181
  }
182
+ if (keyResults !== undefined) result.keyResults = keyResults
183
+ return result
85
184
  }
86
185
 
87
186
  /**
@@ -136,7 +235,7 @@ export function updateKeyResult(
136
235
  krDescription: string,
137
236
  currentValue: number
138
237
  ): OKRDefinition {
139
- const keyResults = okr.keyResults?.map(kr => {
238
+ const keyResults = okr.keyResults?.map((kr) => {
140
239
  if (kr.description === krDescription) {
141
240
  const updatedKR = { ...kr, currentValue }
142
241
  return {
@@ -148,24 +247,24 @@ export function updateKeyResult(
148
247
  })
149
248
 
150
249
  // Recalculate overall status and confidence
151
- const progress = calculateOKRProgress({ ...okr, keyResults })
250
+ const okrWithKeyResults: OKRDefinition = { ...okr }
251
+ if (keyResults !== undefined) okrWithKeyResults.keyResults = keyResults
252
+ const progress = calculateOKRProgress(okrWithKeyResults)
152
253
  const status = determineOKRStatus(progress, okr.confidence || 0)
153
254
 
154
- return {
255
+ const result: OKRDefinition = {
155
256
  ...okr,
156
- keyResults,
157
- status,
158
257
  confidence: calculateConfidence(keyResults || []),
159
258
  }
259
+ if (keyResults !== undefined) result.keyResults = keyResults
260
+ if (status !== undefined) result.status = status
261
+ return result
160
262
  }
161
263
 
162
264
  /**
163
265
  * Determine OKR status based on progress and confidence
164
266
  */
165
- function determineOKRStatus(
166
- progress: number,
167
- confidence: number
168
- ): OKRDefinition['status'] {
267
+ function determineOKRStatus(progress: number, confidence: number): OKRDefinition['status'] {
169
268
  if (progress === 0) return 'not-started'
170
269
  if (progress === 100) return 'completed'
171
270
  if (confidence < 50 || progress < 30) return 'at-risk'
@@ -199,21 +298,21 @@ export function getKeyResultsOnTrack(okr: OKRDefinition): KeyResult[] {
199
298
  * Get key results that are at risk
200
299
  */
201
300
  export function getKeyResultsAtRisk(okr: OKRDefinition): KeyResult[] {
202
- return okr.keyResults?.filter(kr => !isKeyResultOnTrack(kr)) || []
301
+ return okr.keyResults?.filter((kr) => !isKeyResultOnTrack(kr)) || []
203
302
  }
204
303
 
205
304
  /**
206
305
  * Get OKRs by owner
207
306
  */
208
307
  export function getOKRsByOwner(okrs: OKRDefinition[], owner: string): OKRDefinition[] {
209
- return okrs.filter(okr => okr.owner === owner)
308
+ return okrs.filter((okr) => okr.owner === owner)
210
309
  }
211
310
 
212
311
  /**
213
312
  * Get OKRs by period
214
313
  */
215
314
  export function getOKRsByPeriod(okrs: OKRDefinition[], period: string): OKRDefinition[] {
216
- return okrs.filter(okr => okr.period === period)
315
+ return okrs.filter((okr) => okr.period === period)
217
316
  }
218
317
 
219
318
  /**
@@ -223,7 +322,7 @@ export function getOKRsByStatus(
223
322
  okrs: OKRDefinition[],
224
323
  status: OKRDefinition['status']
225
324
  ): OKRDefinition[] {
226
- return okrs.filter(okr => okr.status === status)
325
+ return okrs.filter((okr) => okr.status === status)
227
326
  }
228
327
 
229
328
  /**
@@ -232,9 +331,10 @@ export function getOKRsByStatus(
232
331
  export function calculateSuccessRate(okrs: OKRDefinition[]): number {
233
332
  if (okrs.length === 0) return 0
234
333
 
235
- const avgProgress = okrs.reduce((sum, okr) => {
236
- return sum + calculateOKRProgress(okr)
237
- }, 0) / okrs.length
334
+ const avgProgress =
335
+ okrs.reduce((sum, okr) => {
336
+ return sum + calculateOKRProgress(okr)
337
+ }, 0) / okrs.length
238
338
 
239
339
  return avgProgress
240
340
  }
@@ -741,7 +741,7 @@ export function resolvePermissions(
741
741
  // Search through hierarchy
742
742
  for (const dept of org.departments || []) {
743
743
  for (const t of dept.teams || []) {
744
- const pos = t.positions?.find(p => p.id === positionId)
744
+ const pos = t.positions?.find((p) => p.id === positionId)
745
745
  if (pos) {
746
746
  position = pos
747
747
  team = t
@@ -755,7 +755,7 @@ export function resolvePermissions(
755
755
  // Also check standalone teams
756
756
  if (!position) {
757
757
  for (const t of org.teams || []) {
758
- const pos = t.positions?.find(p => p.id === positionId)
758
+ const pos = t.positions?.find((p) => p.id === positionId)
759
759
  if (pos) {
760
760
  position = pos
761
761
  team = t
@@ -767,7 +767,7 @@ export function resolvePermissions(
767
767
  if (!position) return null
768
768
 
769
769
  // Find the role
770
- const role = org.roles?.find(r => r.id === position.roleId)
770
+ const role = org.roles?.find((r) => r['id'] === position.roleId)
771
771
 
772
772
  // Build inheritance chain
773
773
  const inheritanceChain: string[] = []
@@ -803,16 +803,16 @@ export function resolvePermissions(
803
803
 
804
804
  // 4. Role permissions
805
805
  if (role?.permissions) {
806
- inheritanceChain.push(`role:${role.id}`)
806
+ inheritanceChain.push(`role:${role['id']}`)
807
807
  mergePermissions(permissions, role.permissions)
808
808
  }
809
809
 
810
810
  // 5. Role capabilities
811
- if (role?.canApprove) {
812
- canApprove.push(...role.canApprove)
811
+ if (role?.['canApprove']) {
812
+ canApprove.push(...role['canApprove'])
813
813
  }
814
- if (role?.canHandle) {
815
- canHandle.push(...role.canHandle)
814
+ if (role?.['canHandle']) {
815
+ canHandle.push(...role['canHandle'])
816
816
  }
817
817
 
818
818
  // 6. Position-specific permissions
@@ -867,7 +867,7 @@ export function getApprovalChainForRequest(
867
867
  requestType: string,
868
868
  amount?: number
869
869
  ): ApproverSpec[] {
870
- const chain = org.approvalChains?.find(c => c.type === requestType && c.active !== false)
870
+ const chain = org.approvalChains?.find((c) => c.type === requestType && c.active !== false)
871
871
  if (!chain) return []
872
872
 
873
873
  // Find the appropriate level based on amount
@@ -886,19 +886,16 @@ export function getApprovalChainForRequest(
886
886
  /**
887
887
  * Find manager for a position (follows reportsTo chain)
888
888
  */
889
- export function findManager(
890
- org: Organization,
891
- positionId: string
892
- ): Position | null {
889
+ export function findManager(org: Organization, positionId: string): Position | null {
893
890
  // Find the position
894
891
  for (const dept of org.departments || []) {
895
892
  for (const team of dept.teams || []) {
896
- const position = team.positions?.find(p => p.id === positionId)
893
+ const position = team.positions?.find((p) => p.id === positionId)
897
894
  if (position?.reportsTo) {
898
895
  // Find the manager position
899
896
  for (const d of org.departments || []) {
900
897
  for (const t of d.teams || []) {
901
- const manager = t.positions?.find(p => p.id === position.reportsTo)
898
+ const manager = t.positions?.find((p) => p.id === position.reportsTo)
902
899
  if (manager) return manager
903
900
  }
904
901
  }