business-as-code 2.1.1 → 2.1.3

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 (52) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +8 -0
  3. package/LICENSE +21 -0
  4. package/dist/canvas/activities.d.ts +19 -0
  5. package/dist/canvas/activities.d.ts.map +1 -0
  6. package/dist/canvas/activities.js +20 -0
  7. package/dist/canvas/activities.js.map +1 -0
  8. package/dist/canvas/channels.d.ts +20 -0
  9. package/dist/canvas/channels.d.ts.map +1 -0
  10. package/dist/canvas/channels.js +21 -0
  11. package/dist/canvas/channels.js.map +1 -0
  12. package/dist/canvas/relationships.d.ts +20 -0
  13. package/dist/canvas/relationships.d.ts.map +1 -0
  14. package/dist/canvas/relationships.js +21 -0
  15. package/dist/canvas/relationships.js.map +1 -0
  16. package/dist/canvas/resources.d.ts +20 -0
  17. package/dist/canvas/resources.d.ts.map +1 -0
  18. package/dist/canvas/resources.js +30 -0
  19. package/dist/canvas/resources.js.map +1 -0
  20. package/dist/canvas/revenue.d.ts +22 -0
  21. package/dist/canvas/revenue.d.ts.map +1 -0
  22. package/dist/canvas/revenue.js +30 -0
  23. package/dist/canvas/revenue.js.map +1 -0
  24. package/dist/canvas/segments.d.ts +20 -0
  25. package/dist/canvas/segments.d.ts.map +1 -0
  26. package/dist/canvas/segments.js +28 -0
  27. package/dist/canvas/segments.js.map +1 -0
  28. package/dist/canvas/types.d.ts +232 -0
  29. package/dist/canvas/types.d.ts.map +1 -0
  30. package/dist/canvas/types.js +8 -0
  31. package/dist/canvas/types.js.map +1 -0
  32. package/dist/canvas/value.d.ts +20 -0
  33. package/dist/canvas/value.d.ts.map +1 -0
  34. package/dist/canvas/value.js +21 -0
  35. package/dist/canvas/value.js.map +1 -0
  36. package/dist/entities/planning.d.ts +0 -87
  37. package/package.json +13 -13
  38. package/src/canvas/activities.ts +32 -0
  39. package/src/canvas/canvas.ts +482 -0
  40. package/src/canvas/channels.ts +34 -0
  41. package/src/canvas/costs.ts +43 -0
  42. package/src/canvas/economics.ts +99 -0
  43. package/src/canvas/index.ts +206 -0
  44. package/src/canvas/partnerships.ts +34 -0
  45. package/src/canvas/projections.ts +141 -0
  46. package/src/canvas/relationships.ts +34 -0
  47. package/src/canvas/resources.ts +43 -0
  48. package/src/canvas/revenue.ts +56 -0
  49. package/src/canvas/segments.ts +42 -0
  50. package/src/canvas/types.ts +363 -0
  51. package/src/canvas/value.ts +34 -0
  52. package/tests/canvas.test.ts +842 -0
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Value Propositions
3
+ *
4
+ * Functions for creating and working with value propositions.
5
+ */
6
+ /**
7
+ * Create a value proposition
8
+ */
9
+ export function createValueProposition(input) {
10
+ const { name, description, valueType, benefits = [], targetSegments, quantifiedValue, segmentFit } = input;
11
+ return {
12
+ name,
13
+ description,
14
+ valueType,
15
+ benefits,
16
+ targetSegments,
17
+ quantifiedValue,
18
+ segmentFit,
19
+ };
20
+ }
21
+ //# sourceMappingURL=value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"value.js","sourceRoot":"","sources":["../../src/canvas/value.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAkC;IACvE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,GAAG,EAAE,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,GAAG,KAAK,CAAA;IAE1G,OAAO;QACL,IAAI;QACJ,WAAW;QACX,SAAS;QACT,QAAQ;QACR,cAAc;QACd,eAAe;QACf,UAAU;KACX,CAAA;AACH,CAAC"}
@@ -1,87 +0,0 @@
1
- import type { Noun } from 'ai-database';
2
- /**
3
- * Planning Entities
4
- * Issue, Plan - Planning-focused abstractions for tracking work
5
- *
6
- * These complement the execution-focused Task in digital-tasks
7
- * and the project management entities in projects.ts
8
- *
9
- * Key distinction:
10
- * - Task (digital-tasks): Function execution with workers, queues, runtime
11
- * - Task (projects.ts): Project management work item
12
- * - Issue (planning.ts): Planning-focused with design, acceptance criteria, notes
13
- */
14
- /**
15
- * WorkItem - A planning-focused work item (ticket/issue)
16
- *
17
- * Models the full lifecycle of work from ideation through completion:
18
- * - What: title, description
19
- * - Why: context, business value
20
- * - How: design (implementation approach)
21
- * - Done: acceptance criteria
22
- * - Context: notes (session handoff, progress tracking)
23
- *
24
- * This abstraction is backend-agnostic and can be implemented by:
25
- * - beads (SQLite) - maps to beads Issue
26
- * - Linear - maps to Linear Issue
27
- * - GitHub Issues
28
- * - Jira - maps to Jira Issue/Ticket
29
- * - etc.
30
- *
31
- * Note: Distinct from entities/risk.ts Issue which represents
32
- * operational/business issues (problems requiring resolution).
33
- * WorkItem represents planned development work.
34
- */
35
- export declare const WorkItem: Noun;
36
- /**
37
- * Comment - A comment on an issue
38
- */
39
- export declare const Comment: Noun;
40
- /**
41
- * Event - An audit trail event on an issue
42
- */
43
- export declare const Event: Noun;
44
- /**
45
- * WorkItemComment - Alias for Comment (for clarity)
46
- */
47
- export declare const WorkItemComment: Noun;
48
- /**
49
- * WorkItemEvent - Alias for Event (for clarity)
50
- */
51
- export declare const WorkItemEvent: Noun;
52
- /**
53
- * Plan - A high-level planning document that generates issues
54
- *
55
- * Bridges Strategy → Plan → Issues → Tasks
56
- *
57
- * A Plan captures:
58
- * - Goals and objectives
59
- * - Constraints and assumptions
60
- * - Design decisions
61
- * - Issue breakdown
62
- */
63
- export declare const Plan: Noun;
64
- /**
65
- * Dependency types for issue relationships
66
- */
67
- export declare const DependencyTypes: {
68
- /** Hard blocker - issue A blocks issue B from starting */
69
- readonly blocks: "blocks";
70
- /** Soft link - issues are related but not blocking */
71
- readonly related: "related";
72
- /** Hierarchical - epic/subtask relationship */
73
- readonly parentChild: "parent-child";
74
- /** Provenance - issue B discovered while working on A */
75
- readonly discoveredFrom: "discovered-from";
76
- };
77
- export type DependencyType = (typeof DependencyTypes)[keyof typeof DependencyTypes];
78
- export declare const PlanningEntities: {
79
- WorkItem: Noun;
80
- WorkItemComment: Noun;
81
- WorkItemEvent: Noun;
82
- Comment: Noun;
83
- Event: Noun;
84
- Plan: Noun;
85
- };
86
- export default PlanningEntities;
87
- //# sourceMappingURL=planning.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "business-as-code",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "Primitives for expressing business logic and processes as code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,17 +11,9 @@
11
11
  "types": "./dist/index.d.ts"
12
12
  }
13
13
  },
14
- "scripts": {
15
- "build": "tsc",
16
- "dev": "tsc --watch",
17
- "test": "vitest",
18
- "typecheck": "tsc --noEmit",
19
- "lint": "eslint .",
20
- "clean": "rm -rf dist"
21
- },
22
14
  "dependencies": {
23
- "ai-database": "2.1.1",
24
- "ai-functions": "2.1.1"
15
+ "ai-database": "2.1.3",
16
+ "ai-functions": "2.1.3"
25
17
  },
26
18
  "keywords": [
27
19
  "business",
@@ -29,5 +21,13 @@
29
21
  "code",
30
22
  "primitives"
31
23
  ],
32
- "license": "MIT"
33
- }
24
+ "license": "MIT",
25
+ "scripts": {
26
+ "build": "tsc",
27
+ "dev": "tsc --watch",
28
+ "test": "vitest",
29
+ "typecheck": "tsc --noEmit",
30
+ "lint": "eslint .",
31
+ "clean": "rm -rf dist"
32
+ }
33
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Key Activities
3
+ *
4
+ * Functions for creating and working with key activities.
5
+ */
6
+
7
+ import type { KeyActivity, ActivityCategory } from './types.js'
8
+
9
+ export interface CreateKeyActivityInput {
10
+ name: string
11
+ category: ActivityCategory
12
+ description?: string
13
+ resources?: string[]
14
+ metrics?: string[]
15
+ uptime?: number
16
+ }
17
+
18
+ /**
19
+ * Create a key activity
20
+ */
21
+ export function createKeyActivity(input: CreateKeyActivityInput): KeyActivity {
22
+ const { name, category, description, resources, metrics, uptime } = input
23
+
24
+ return {
25
+ name,
26
+ category,
27
+ description,
28
+ resources,
29
+ metrics,
30
+ uptime,
31
+ }
32
+ }
@@ -0,0 +1,482 @@
1
+ /**
2
+ * Business Model Canvas
3
+ *
4
+ * Functions for creating, validating, and analyzing the Business Model Canvas.
5
+ */
6
+
7
+ import type {
8
+ BusinessModelCanvas,
9
+ CanvasValidation,
10
+ CanvasSummary,
11
+ CanvasStrengthAnalysis,
12
+ CanvasGaps,
13
+ CustomerSegment,
14
+ ValueProposition,
15
+ Channel,
16
+ CustomerRelationship,
17
+ RevenueStream,
18
+ KeyResource,
19
+ KeyActivity,
20
+ KeyPartnership,
21
+ CostItem,
22
+ } from './types.js'
23
+
24
+ export interface CreateCanvasInput {
25
+ name: string
26
+ customerSegments: CustomerSegment[]
27
+ valuePropositions: ValueProposition[]
28
+ channels: Channel[]
29
+ customerRelationships: CustomerRelationship[]
30
+ revenueStreams: RevenueStream[]
31
+ keyResources: KeyResource[]
32
+ keyActivities: KeyActivity[]
33
+ keyPartnerships: KeyPartnership[]
34
+ costStructure: CostItem[]
35
+ metadata?: Record<string, unknown>
36
+ }
37
+
38
+ /**
39
+ * Create a Business Model Canvas
40
+ */
41
+ export function createCanvas(input: CreateCanvasInput): BusinessModelCanvas {
42
+ return {
43
+ name: input.name,
44
+ customerSegments: input.customerSegments,
45
+ valuePropositions: input.valuePropositions,
46
+ channels: input.channels,
47
+ customerRelationships: input.customerRelationships,
48
+ revenueStreams: input.revenueStreams,
49
+ keyResources: input.keyResources,
50
+ keyActivities: input.keyActivities,
51
+ keyPartnerships: input.keyPartnerships,
52
+ costStructure: input.costStructure,
53
+ metadata: input.metadata,
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Validate canvas completeness
59
+ */
60
+ export function validateCanvas(canvas: BusinessModelCanvas): CanvasValidation {
61
+ const missingBlocks: string[] = []
62
+ const warnings: string[] = []
63
+
64
+ // Check each building block
65
+ if (canvas.customerSegments.length === 0) {
66
+ missingBlocks.push('customerSegments')
67
+ }
68
+
69
+ if (canvas.valuePropositions.length === 0) {
70
+ missingBlocks.push('valuePropositions')
71
+ }
72
+
73
+ if (canvas.channels.length === 0) {
74
+ missingBlocks.push('channels')
75
+ }
76
+
77
+ if (canvas.customerRelationships.length === 0) {
78
+ missingBlocks.push('customerRelationships')
79
+ }
80
+
81
+ if (canvas.revenueStreams.length === 0) {
82
+ missingBlocks.push('revenueStreams')
83
+ }
84
+
85
+ if (canvas.keyResources.length === 0) {
86
+ missingBlocks.push('keyResources')
87
+ }
88
+
89
+ if (canvas.keyActivities.length === 0) {
90
+ missingBlocks.push('keyActivities')
91
+ }
92
+
93
+ if (canvas.keyPartnerships.length === 0) {
94
+ missingBlocks.push('keyPartnerships')
95
+ }
96
+
97
+ if (canvas.costStructure.length === 0) {
98
+ missingBlocks.push('costStructure')
99
+ }
100
+
101
+ // Add warnings for incomplete data
102
+ if (canvas.customerSegments.some(s => !s.size)) {
103
+ warnings.push('Some customer segments are missing size estimates')
104
+ }
105
+
106
+ if (canvas.revenueStreams.some(r => !r.price && !r.pricePerUnit)) {
107
+ warnings.push('Some revenue streams are missing pricing information')
108
+ }
109
+
110
+ return {
111
+ isComplete: missingBlocks.length === 0,
112
+ missingBlocks,
113
+ warnings,
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Generate canvas summary with key metrics
119
+ */
120
+ export function generateCanvasSummary(canvas: BusinessModelCanvas): CanvasSummary {
121
+ // Calculate Total Addressable Market (TAM)
122
+ const totalAddressableMarket = canvas.customerSegments.reduce((sum, segment) => {
123
+ if (segment.size && segment.revenuePerCustomer) {
124
+ return sum + segment.size * segment.revenuePerCustomer
125
+ }
126
+ return sum
127
+ }, 0)
128
+
129
+ // Calculate projected annual revenue from revenue streams
130
+ const projectedAnnualRevenue = canvas.revenueStreams.reduce((sum, stream) => {
131
+ return sum + (stream.annualRevenue ?? 0)
132
+ }, 0)
133
+
134
+ // Calculate total annual costs
135
+ const totalAnnualCosts = canvas.costStructure.reduce((sum, cost) => {
136
+ if (cost.amount) {
137
+ // Normalize to annual
138
+ const multiplier = cost.period === 'monthly' ? 12 : cost.period === 'quarterly' ? 4 : 1
139
+ return sum + cost.amount * multiplier
140
+ }
141
+ if (cost.estimatedTotal) {
142
+ return sum + cost.estimatedTotal
143
+ }
144
+ return sum
145
+ }, 0)
146
+
147
+ // Calculate projected profit
148
+ const projectedProfit = projectedAnnualRevenue - totalAnnualCosts
149
+
150
+ return {
151
+ totalAddressableMarket,
152
+ projectedAnnualRevenue,
153
+ totalAnnualCosts,
154
+ projectedProfit,
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Analyze canvas strength
160
+ */
161
+ export function analyzeCanvasStrength(canvas: BusinessModelCanvas): CanvasStrengthAnalysis {
162
+ const blockScores: Record<string, number> = {}
163
+ const strengths: string[] = []
164
+ const weaknesses: string[] = []
165
+
166
+ // Score customer segments (0-100)
167
+ const segmentScore = scoreCustomerSegments(canvas.customerSegments)
168
+ blockScores['customerSegments'] = segmentScore
169
+ if (segmentScore >= 70) {
170
+ strengths.push('Well-defined customer segments')
171
+ } else if (segmentScore < 40) {
172
+ weaknesses.push('Customer segments need more definition')
173
+ }
174
+
175
+ // Score value propositions
176
+ const valueScore = scoreValuePropositions(canvas.valuePropositions)
177
+ blockScores['valuePropositions'] = valueScore
178
+ if (valueScore >= 70) {
179
+ strengths.push('Strong value propositions')
180
+ } else if (valueScore < 40) {
181
+ weaknesses.push('Value propositions need strengthening')
182
+ }
183
+
184
+ // Score channels
185
+ const channelScore = scoreChannels(canvas.channels)
186
+ blockScores['channels'] = channelScore
187
+ if (channelScore >= 70) {
188
+ strengths.push('Diverse channel strategy')
189
+ } else if (channelScore < 40) {
190
+ weaknesses.push('Limited channel coverage')
191
+ }
192
+
193
+ // Score relationships
194
+ const relationshipScore = scoreRelationships(canvas.customerRelationships)
195
+ blockScores['customerRelationships'] = relationshipScore
196
+ if (relationshipScore >= 70) {
197
+ strengths.push('Strong customer relationship strategy')
198
+ } else if (relationshipScore < 40) {
199
+ weaknesses.push('Customer relationship strategy needs work')
200
+ }
201
+
202
+ // Score revenue streams
203
+ const revenueScore = scoreRevenueStreams(canvas.revenueStreams)
204
+ blockScores['revenueStreams'] = revenueScore
205
+ if (revenueScore >= 70) {
206
+ strengths.push('Solid revenue model')
207
+ } else if (revenueScore < 40) {
208
+ weaknesses.push('Revenue model needs development')
209
+ }
210
+
211
+ // Score resources
212
+ const resourceScore = scoreResources(canvas.keyResources)
213
+ blockScores['keyResources'] = resourceScore
214
+ if (resourceScore >= 70) {
215
+ strengths.push('Strong resource base')
216
+ } else if (resourceScore < 40) {
217
+ weaknesses.push('Key resources are lacking')
218
+ }
219
+
220
+ // Score activities
221
+ const activityScore = scoreActivities(canvas.keyActivities)
222
+ blockScores['keyActivities'] = activityScore
223
+ if (activityScore >= 70) {
224
+ strengths.push('Clear key activities')
225
+ } else if (activityScore < 40) {
226
+ weaknesses.push('Key activities need clarification')
227
+ }
228
+
229
+ // Score partnerships
230
+ const partnershipScore = scorePartnerships(canvas.keyPartnerships)
231
+ blockScores['keyPartnerships'] = partnershipScore
232
+ if (partnershipScore >= 70) {
233
+ strengths.push('Strong partnership ecosystem')
234
+ } else if (partnershipScore < 40) {
235
+ weaknesses.push('Partnership strategy needs development')
236
+ }
237
+
238
+ // Score cost structure
239
+ const costScore = scoreCostStructure(canvas.costStructure)
240
+ blockScores['costStructure'] = costScore
241
+ if (costScore >= 70) {
242
+ strengths.push('Well-understood cost structure')
243
+ } else if (costScore < 40) {
244
+ weaknesses.push('Cost structure needs more detail')
245
+ }
246
+
247
+ // Calculate overall score
248
+ const scores = Object.values(blockScores)
249
+ const overallScore = scores.reduce((sum, s) => sum + s, 0) / scores.length
250
+
251
+ return {
252
+ overallScore,
253
+ strengths,
254
+ weaknesses,
255
+ blockScores,
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Identify gaps in the canvas
261
+ */
262
+ export function identifyCanvasGaps(canvas: BusinessModelCanvas): CanvasGaps {
263
+ const criticalGaps: string[] = []
264
+ const warnings: string[] = []
265
+ const recommendations: string[] = []
266
+
267
+ // Check for critical gaps (empty building blocks)
268
+ if (canvas.valuePropositions.length === 0) {
269
+ criticalGaps.push('valuePropositions')
270
+ recommendations.push('Define at least one clear value proposition for your target customers')
271
+ }
272
+
273
+ if (canvas.customerSegments.length === 0) {
274
+ criticalGaps.push('customerSegments')
275
+ recommendations.push('Identify and define your target customer segments')
276
+ }
277
+
278
+ if (canvas.revenueStreams.length === 0) {
279
+ criticalGaps.push('revenueStreams')
280
+ recommendations.push('Define how you will generate revenue from your customers')
281
+ }
282
+
283
+ if (canvas.customerRelationships.length === 0) {
284
+ criticalGaps.push('customerRelationships')
285
+ recommendations.push('Define how you will acquire, retain, and grow customer relationships')
286
+ }
287
+
288
+ if (canvas.keyResources.length === 0) {
289
+ criticalGaps.push('keyResources')
290
+ recommendations.push('Identify the key resources required to deliver your value proposition')
291
+ }
292
+
293
+ if (canvas.keyActivities.length === 0) {
294
+ criticalGaps.push('keyActivities')
295
+ recommendations.push('Define the key activities required to operate your business')
296
+ }
297
+
298
+ if (canvas.keyPartnerships.length === 0) {
299
+ criticalGaps.push('keyPartnerships')
300
+ recommendations.push('Consider strategic partnerships that could enhance your business')
301
+ }
302
+
303
+ if (canvas.channels.length === 0) {
304
+ criticalGaps.push('channels')
305
+ recommendations.push('Define channels to reach and serve your customers')
306
+ }
307
+
308
+ if (canvas.costStructure.length === 0) {
309
+ criticalGaps.push('costStructure')
310
+ recommendations.push('Map out your cost structure to understand your business economics')
311
+ }
312
+
313
+ // Check for warnings (incomplete data)
314
+ const hasRecurringRevenue = canvas.revenueStreams.some(r => r.type === 'recurring')
315
+ if (!hasRecurringRevenue && canvas.revenueStreams.length > 0) {
316
+ warnings.push('No recurring revenue streams - consider subscription or usage-based models')
317
+ recommendations.push('Explore recurring revenue models for more predictable cash flow')
318
+ }
319
+
320
+ const hasIntellectualProperty = canvas.keyResources.some(r => r.type === 'intellectual')
321
+ if (!hasIntellectualProperty && canvas.keyResources.length > 0) {
322
+ warnings.push('No intellectual property identified - consider what makes your offering unique')
323
+ recommendations.push('Identify and protect your intellectual property assets')
324
+ }
325
+
326
+ return {
327
+ criticalGaps,
328
+ warnings,
329
+ recommendations,
330
+ }
331
+ }
332
+
333
+ // =============================================================================
334
+ // Helper scoring functions
335
+ // =============================================================================
336
+
337
+ function scoreCustomerSegments(segments: CustomerSegment[]): number {
338
+ if (segments.length === 0) return 0
339
+ let score = 30 // Base score for having segments
340
+
341
+ // Add points for segment details
342
+ const hasSize = segments.some(s => s.size)
343
+ const hasRevenue = segments.some(s => s.revenuePerCustomer)
344
+ const hasCharacteristics = segments.some(s => s.characteristics && s.characteristics.length > 0)
345
+
346
+ if (hasSize) score += 20
347
+ if (hasRevenue) score += 25
348
+ if (hasCharacteristics) score += 15
349
+
350
+ // Bonus for multiple segments
351
+ if (segments.length >= 2) score += 10
352
+
353
+ return Math.min(score, 100)
354
+ }
355
+
356
+ function scoreValuePropositions(props: ValueProposition[]): number {
357
+ if (props.length === 0) return 0
358
+ let score = 30
359
+
360
+ const hasBenefits = props.some(p => p.benefits.length >= 2)
361
+ const hasValueType = props.some(p => p.valueType)
362
+ const hasSegmentFit = props.some(p => p.segmentFit)
363
+ const hasQuantified = props.some(p => p.quantifiedValue)
364
+
365
+ if (hasBenefits) score += 20
366
+ if (hasValueType) score += 15
367
+ if (hasSegmentFit) score += 20
368
+ if (hasQuantified) score += 15
369
+
370
+ return Math.min(score, 100)
371
+ }
372
+
373
+ function scoreChannels(channels: Channel[]): number {
374
+ if (channels.length === 0) return 0
375
+ let score = 30
376
+
377
+ const hasPhases = channels.some(c => c.phases && c.phases.length > 0)
378
+ const hasCost = channels.some(c => c.costPerAcquisition)
379
+ const hasMultipleTypes = new Set(channels.map(c => c.type)).size >= 2
380
+
381
+ if (hasPhases) score += 20
382
+ if (hasCost) score += 25
383
+ if (hasMultipleTypes) score += 25
384
+
385
+ return Math.min(score, 100)
386
+ }
387
+
388
+ function scoreRelationships(relationships: CustomerRelationship[]): number {
389
+ if (relationships.length === 0) return 0
390
+ let score = 40
391
+
392
+ const hasAutomation = relationships.some(r => r.automationLevel)
393
+ const hasSegment = relationships.some(r => r.segment)
394
+ const hasMultipleTypes = new Set(relationships.map(r => r.type)).size >= 2
395
+
396
+ if (hasAutomation) score += 20
397
+ if (hasSegment) score += 20
398
+ if (hasMultipleTypes) score += 20
399
+
400
+ return Math.min(score, 100)
401
+ }
402
+
403
+ function scoreRevenueStreams(streams: RevenueStream[]): number {
404
+ if (streams.length === 0) return 0
405
+ let score = 30
406
+
407
+ const hasRecurring = streams.some(s => s.type === 'recurring')
408
+ const hasPrice = streams.some(s => s.price || s.pricePerUnit)
409
+ const hasCustomers = streams.some(s => s.activeCustomers)
410
+ const hasTiers = streams.some(s => s.tiers && s.tiers.length > 0)
411
+
412
+ if (hasRecurring) score += 25
413
+ if (hasPrice) score += 20
414
+ if (hasCustomers) score += 15
415
+ if (hasTiers) score += 10
416
+
417
+ return Math.min(score, 100)
418
+ }
419
+
420
+ function scoreResources(resources: KeyResource[]): number {
421
+ if (resources.length === 0) return 0
422
+ let score = 30
423
+
424
+ const hasIntellectual = resources.some(r => r.type === 'intellectual')
425
+ const hasHuman = resources.some(r => r.type === 'human')
426
+ const hasCost = resources.some(r => r.totalCost || r.cost)
427
+ const hasMultipleTypes = new Set(resources.map(r => r.type)).size >= 2
428
+
429
+ if (hasIntellectual) score += 20
430
+ if (hasHuman) score += 15
431
+ if (hasCost) score += 20
432
+ if (hasMultipleTypes) score += 15
433
+
434
+ return Math.min(score, 100)
435
+ }
436
+
437
+ function scoreActivities(activities: KeyActivity[]): number {
438
+ if (activities.length === 0) return 0
439
+ let score = 40
440
+
441
+ const hasResources = activities.some(a => a.resources && a.resources.length > 0)
442
+ const hasMetrics = activities.some(a => a.metrics && a.metrics.length > 0)
443
+ const hasDescription = activities.some(a => a.description)
444
+
445
+ if (hasResources) score += 20
446
+ if (hasMetrics) score += 25
447
+ if (hasDescription) score += 15
448
+
449
+ return Math.min(score, 100)
450
+ }
451
+
452
+ function scorePartnerships(partnerships: KeyPartnership[]): number {
453
+ if (partnerships.length === 0) return 0
454
+ let score = 40
455
+
456
+ const hasBenefits = partnerships.some(p => p.benefits && p.benefits.length > 0)
457
+ const hasPurpose = partnerships.some(p => p.purpose)
458
+ const hasContract = partnerships.some(p => p.contractValue)
459
+
460
+ if (hasBenefits) score += 20
461
+ if (hasPurpose) score += 20
462
+ if (hasContract) score += 20
463
+
464
+ return Math.min(score, 100)
465
+ }
466
+
467
+ function scoreCostStructure(costs: CostItem[]): number {
468
+ if (costs.length === 0) return 0
469
+ let score = 30
470
+
471
+ const hasFixed = costs.some(c => c.category === 'fixed')
472
+ const hasVariable = costs.some(c => c.category === 'variable')
473
+ const hasAmounts = costs.some(c => c.amount || c.estimatedTotal)
474
+ const hasPeriod = costs.some(c => c.period)
475
+
476
+ if (hasFixed) score += 15
477
+ if (hasVariable) score += 20
478
+ if (hasAmounts) score += 25
479
+ if (hasPeriod) score += 10
480
+
481
+ return Math.min(score, 100)
482
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Channels
3
+ *
4
+ * Functions for creating and working with channels.
5
+ */
6
+
7
+ import type { Channel, ChannelType, ChannelPhase } from './types.js'
8
+
9
+ export interface CreateChannelInput {
10
+ name: string
11
+ type: ChannelType
12
+ phases?: ChannelPhase[]
13
+ costPerAcquisition?: number
14
+ partnerType?: string
15
+ revenueShare?: number
16
+ conversionRate?: number
17
+ }
18
+
19
+ /**
20
+ * Create a channel
21
+ */
22
+ export function createChannel(input: CreateChannelInput): Channel {
23
+ const { name, type, phases, costPerAcquisition, partnerType, revenueShare, conversionRate } = input
24
+
25
+ return {
26
+ name,
27
+ type,
28
+ phases,
29
+ costPerAcquisition,
30
+ partnerType,
31
+ revenueShare,
32
+ conversionRate,
33
+ }
34
+ }