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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/dist/canvas/activities.d.ts +19 -0
- package/dist/canvas/activities.d.ts.map +1 -0
- package/dist/canvas/activities.js +20 -0
- package/dist/canvas/activities.js.map +1 -0
- package/dist/canvas/channels.d.ts +20 -0
- package/dist/canvas/channels.d.ts.map +1 -0
- package/dist/canvas/channels.js +21 -0
- package/dist/canvas/channels.js.map +1 -0
- package/dist/canvas/relationships.d.ts +20 -0
- package/dist/canvas/relationships.d.ts.map +1 -0
- package/dist/canvas/relationships.js +21 -0
- package/dist/canvas/relationships.js.map +1 -0
- package/dist/canvas/resources.d.ts +20 -0
- package/dist/canvas/resources.d.ts.map +1 -0
- package/dist/canvas/resources.js +30 -0
- package/dist/canvas/resources.js.map +1 -0
- package/dist/canvas/revenue.d.ts +22 -0
- package/dist/canvas/revenue.d.ts.map +1 -0
- package/dist/canvas/revenue.js +30 -0
- package/dist/canvas/revenue.js.map +1 -0
- package/dist/canvas/segments.d.ts +20 -0
- package/dist/canvas/segments.d.ts.map +1 -0
- package/dist/canvas/segments.js +28 -0
- package/dist/canvas/segments.js.map +1 -0
- package/dist/canvas/types.d.ts +232 -0
- package/dist/canvas/types.d.ts.map +1 -0
- package/dist/canvas/types.js +8 -0
- package/dist/canvas/types.js.map +1 -0
- package/dist/canvas/value.d.ts +20 -0
- package/dist/canvas/value.d.ts.map +1 -0
- package/dist/canvas/value.js +21 -0
- package/dist/canvas/value.js.map +1 -0
- package/dist/entities/planning.d.ts +0 -87
- package/package.json +13 -13
- package/src/canvas/activities.ts +32 -0
- package/src/canvas/canvas.ts +482 -0
- package/src/canvas/channels.ts +34 -0
- package/src/canvas/costs.ts +43 -0
- package/src/canvas/economics.ts +99 -0
- package/src/canvas/index.ts +206 -0
- package/src/canvas/partnerships.ts +34 -0
- package/src/canvas/projections.ts +141 -0
- package/src/canvas/relationships.ts +34 -0
- package/src/canvas/resources.ts +43 -0
- package/src/canvas/revenue.ts +56 -0
- package/src/canvas/segments.ts +42 -0
- package/src/canvas/types.ts +363 -0
- package/src/canvas/value.ts +34 -0
- 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.
|
|
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.
|
|
24
|
-
"ai-functions": "2.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
|
+
}
|