@elevasis/core 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +108 -0
- package/dist/index.js +239 -27
- package/dist/knowledge/index.d.ts +55 -1
- package/dist/organization-model/index.d.ts +108 -0
- package/dist/organization-model/index.js +239 -27
- package/dist/test-utils/index.d.ts +54 -0
- package/dist/test-utils/index.js +238 -27
- package/package.json +1 -1
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +17 -5
- package/src/business/acquisition/api-schemas.test.ts +125 -14
- package/src/business/acquisition/api-schemas.ts +161 -11
- package/src/business/acquisition/build-templates.test.ts +28 -0
- package/src/business/acquisition/build-templates.ts +20 -8
- package/src/business/acquisition/derive-actions.test.ts +1 -1
- package/src/business/acquisition/types.ts +7 -2
- package/src/business/deals/api-schemas.ts +2 -2
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.test.ts +55 -0
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +107 -41
- package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.test.ts +48 -0
- package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.ts +99 -0
- package/src/execution/engine/tools/integration/server/adapters/apollo/index.ts +1 -0
- package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.test.ts +18 -0
- package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.ts +194 -0
- package/src/execution/engine/tools/integration/server/adapters/clickup/index.ts +7 -0
- package/src/integrations/credentials/api-schemas.ts +21 -2
- package/src/integrations/credentials/schemas.ts +200 -164
- package/src/organization-model/__tests__/graph.test.ts +108 -2
- package/src/organization-model/__tests__/prospecting-ssot.test.ts +12 -12
- package/src/organization-model/__tests__/schema.test.ts +122 -0
- package/src/organization-model/__tests__/surface-projection.test.ts +174 -0
- package/src/organization-model/domains/prospecting.ts +273 -41
- package/src/organization-model/domains/sales.ts +32 -8
- package/src/organization-model/graph/build.ts +74 -0
- package/src/organization-model/graph/schema.ts +1 -0
- package/src/organization-model/graph/types.ts +1 -0
- package/src/organization-model/schema.ts +63 -0
- package/src/organization-model/surface-projection.ts +218 -0
- package/src/platform/constants/versions.ts +1 -1
- package/src/reference/_generated/contracts.md +17 -5
- package/src/server.ts +2 -0
|
@@ -4,7 +4,36 @@ import { DescriptionSchema, DisplayMetadataSchema, ModelIdSchema } from './share
|
|
|
4
4
|
|
|
5
5
|
export const ProspectingLifecycleStageSchema = DisplayMetadataSchema.extend({
|
|
6
6
|
id: ModelIdSchema,
|
|
7
|
-
order: z.number().
|
|
7
|
+
order: z.number().min(0)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export const RecordColumnConfigSchema = z.object({
|
|
11
|
+
key: ModelIdSchema,
|
|
12
|
+
label: z.string().trim().min(1).max(120),
|
|
13
|
+
path: z.string().trim().min(1).max(500),
|
|
14
|
+
width: z.union([z.number().positive(), z.string().trim().min(1).max(100)]).optional(),
|
|
15
|
+
renderType: z.enum(['text', 'badge', 'datetime', 'count', 'json']).optional(),
|
|
16
|
+
badgeColor: z.string().trim().min(1).max(40).optional()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export const RecordColumnsConfigSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
company: z.array(RecordColumnConfigSchema).optional(),
|
|
22
|
+
contact: z.array(RecordColumnConfigSchema).optional()
|
|
23
|
+
})
|
|
24
|
+
.refine((columns) => Boolean(columns.company?.length || columns.contact?.length), {
|
|
25
|
+
message: 'recordColumns must include at least one entity column set'
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const CredentialRequirementSchema = z.object({
|
|
29
|
+
key: ModelIdSchema,
|
|
30
|
+
provider: ModelIdSchema,
|
|
31
|
+
credentialType: z.enum(['api-key', 'api-key-secret', 'oauth', 'webhook-secret']),
|
|
32
|
+
label: z.string().trim().min(1).max(120),
|
|
33
|
+
required: z.boolean(),
|
|
34
|
+
selectionMode: z.enum(['single', 'multiple']).optional(),
|
|
35
|
+
inputPath: z.string().trim().min(1).max(500),
|
|
36
|
+
verifyOnRun: z.boolean().optional()
|
|
8
37
|
})
|
|
9
38
|
|
|
10
39
|
export const ProspectingBuildTemplateStepSchema = DisplayMetadataSchema.extend({
|
|
@@ -12,11 +41,16 @@ export const ProspectingBuildTemplateStepSchema = DisplayMetadataSchema.extend({
|
|
|
12
41
|
primaryEntity: z.enum(['company', 'contact']),
|
|
13
42
|
outputs: z.array(z.enum(['company', 'contact', 'export'])).min(1),
|
|
14
43
|
stageKey: ModelIdSchema,
|
|
44
|
+
recordEntity: z.enum(['company', 'contact']).optional(),
|
|
45
|
+
recordsStageKey: ModelIdSchema.optional(),
|
|
46
|
+
recordSourceStageKey: ModelIdSchema.optional(),
|
|
15
47
|
dependsOn: z.array(ModelIdSchema).optional(),
|
|
16
48
|
dependencyMode: z.literal('per-record-eligibility'),
|
|
17
49
|
capabilityKey: ModelIdSchema,
|
|
18
50
|
defaultBatchSize: z.number().int().positive(),
|
|
19
|
-
maxBatchSize: z.number().int().positive()
|
|
51
|
+
maxBatchSize: z.number().int().positive(),
|
|
52
|
+
recordColumns: RecordColumnsConfigSchema.optional(),
|
|
53
|
+
credentialRequirements: z.array(CredentialRequirementSchema).optional()
|
|
20
54
|
}).refine((step) => step.defaultBatchSize <= step.maxBatchSize, {
|
|
21
55
|
message: 'defaultBatchSize must be less than or equal to maxBatchSize',
|
|
22
56
|
path: ['defaultBatchSize']
|
|
@@ -28,24 +62,163 @@ export const ProspectingBuildTemplateSchema = DisplayMetadataSchema.extend({
|
|
|
28
62
|
})
|
|
29
63
|
|
|
30
64
|
export type ListBuilderStep = z.infer<typeof ProspectingBuildTemplateStepSchema>
|
|
65
|
+
export type RecordColumnConfig = z.infer<typeof RecordColumnConfigSchema>
|
|
66
|
+
export type CredentialRequirement = z.infer<typeof CredentialRequirementSchema>
|
|
31
67
|
export type TemplateName = 'localServices' | 'dtcApolloClickup'
|
|
32
68
|
export type StepName = string
|
|
33
|
-
export type CapabilityRegistry = Record<string, string>
|
|
34
69
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
70
|
+
const DTC_RECORD_COLUMNS = {
|
|
71
|
+
populated: {
|
|
72
|
+
company: [
|
|
73
|
+
{ key: 'name', label: 'Company', path: 'company.name' },
|
|
74
|
+
{ key: 'domain', label: 'Domain', path: 'company.domain' },
|
|
75
|
+
{ key: 'employee-count', label: 'Employees', path: 'company.numEmployees', renderType: 'count' },
|
|
76
|
+
{ key: 'apollo-industry', label: 'Apollo industry', path: 'company.category' },
|
|
77
|
+
{ key: 'location', label: 'Location', path: 'company.locationState' }
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
crawled: {
|
|
81
|
+
company: [
|
|
82
|
+
{ key: 'name', label: 'Company', path: 'company.name' },
|
|
83
|
+
{ key: 'domain', label: 'Domain', path: 'company.domain' },
|
|
84
|
+
{ key: 'page-count', label: 'Pages', path: 'company.enrichmentData.websiteCrawl.pageCount', renderType: 'count' },
|
|
85
|
+
{ key: 'crawl-status', label: 'Crawl status', path: 'processingState.crawled.status', renderType: 'badge' }
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
extracted: {
|
|
89
|
+
company: [
|
|
90
|
+
{ key: 'name', label: 'Company', path: 'company.name' },
|
|
91
|
+
{ key: 'domain', label: 'Domain', path: 'company.domain' },
|
|
92
|
+
{ key: 'description', label: 'Description', path: 'company.enrichmentData.websiteCrawl.companyDescription' },
|
|
93
|
+
{ key: 'services', label: 'Services', path: 'company.enrichmentData.websiteCrawl.services', renderType: 'json' },
|
|
94
|
+
{ key: 'automation-gaps', label: 'Automation gaps', path: 'company.enrichmentData.websiteCrawl.automationGaps', renderType: 'json' },
|
|
95
|
+
{ key: 'contact-count', label: 'Contacts', path: 'company.enrichmentData.websiteCrawl.emailCount', renderType: 'count' }
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
qualified: {
|
|
99
|
+
company: [
|
|
100
|
+
{ key: 'name', label: 'Company', path: 'company.name' },
|
|
101
|
+
{ key: 'domain', label: 'Domain', path: 'company.domain' },
|
|
102
|
+
{ key: 'score', label: 'Score', path: 'company.qualificationScore', renderType: 'badge', badgeColor: 'green' },
|
|
103
|
+
{ key: 'signals', label: 'Signals', path: 'company.qualificationSignals', renderType: 'json' },
|
|
104
|
+
{ key: 'disqualified-reason', label: 'Disqualified reason', path: 'processingState.qualified.data.disqualifiedReason' }
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
decisionMakers: {
|
|
108
|
+
contact: [
|
|
109
|
+
{ key: 'name', label: 'Name', path: 'contact.name' },
|
|
110
|
+
{ key: 'title', label: 'Title', path: 'contact.title' },
|
|
111
|
+
{ key: 'email', label: 'Email', path: 'contact.email' },
|
|
112
|
+
{ key: 'linkedin', label: 'LinkedIn', path: 'contact.linkedinUrl' },
|
|
113
|
+
{ key: 'priority-score', label: 'Priority', path: 'contact.enrichmentData.apollo.priorityScore', renderType: 'badge' }
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
uploaded: {
|
|
117
|
+
company: [
|
|
118
|
+
{ key: 'name', label: 'Company', path: 'company.name' },
|
|
119
|
+
{ key: 'domain', label: 'Domain', path: 'company.domain' },
|
|
120
|
+
{ key: 'contacts', label: 'Contacts', path: 'company.enrichmentData.approvedLeadListExport.contacts', renderType: 'json' },
|
|
121
|
+
{ key: 'score', label: 'Score', path: 'company.qualificationScore', renderType: 'badge', badgeColor: 'green' },
|
|
122
|
+
{ key: 'approval', label: 'Approval', path: 'company.enrichmentData.approvedLeadListExport.approvalStatus', renderType: 'badge' }
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
} as const satisfies Record<string, z.infer<typeof RecordColumnsConfigSchema>>
|
|
126
|
+
|
|
127
|
+
export const CapabilitySchema = z.object({
|
|
128
|
+
id: ModelIdSchema,
|
|
129
|
+
label: z.string(),
|
|
130
|
+
description: z.string(),
|
|
131
|
+
resourceId: ModelIdSchema
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
export type Capability = z.infer<typeof CapabilitySchema>
|
|
135
|
+
export type CapabilityRegistry = Capability[]
|
|
136
|
+
|
|
137
|
+
export const CAPABILITY_REGISTRY: CapabilityRegistry = [
|
|
138
|
+
{
|
|
139
|
+
id: 'lead-gen.company.source',
|
|
140
|
+
label: 'Source companies',
|
|
141
|
+
description: 'Import source companies from a list provider.',
|
|
142
|
+
resourceId: 'lgn-import-workflow'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'lead-gen.company.apollo-import',
|
|
146
|
+
label: 'Import from Apollo',
|
|
147
|
+
description: 'Pull companies and seed contact data from an Apollo search or list.',
|
|
148
|
+
resourceId: 'lgn-01c-apollo-import-workflow'
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: 'lead-gen.contact.discover',
|
|
152
|
+
label: 'Discover contact emails',
|
|
153
|
+
description: 'Find email addresses for contacts at qualified companies.',
|
|
154
|
+
resourceId: 'lgn-04-email-discovery-workflow'
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: 'lead-gen.contact.verify-email',
|
|
158
|
+
label: 'Verify emails',
|
|
159
|
+
description: 'Check email deliverability before outreach.',
|
|
160
|
+
resourceId: 'lgn-05-email-verification-workflow'
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'lead-gen.company.apify-crawl',
|
|
164
|
+
label: 'Crawl websites',
|
|
165
|
+
description:
|
|
166
|
+
'Crawl company websites via Apify and store raw page markdown in enrichmentData.websiteCrawl.pages for downstream LLM analysis.',
|
|
167
|
+
resourceId: 'lgn-02a-apify-website-crawl-workflow'
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'lead-gen.company.website-extract',
|
|
171
|
+
label: 'Extract website signals',
|
|
172
|
+
description: 'Scrape and analyze company websites for qualification signals.',
|
|
173
|
+
resourceId: 'lgn-02-website-extract-workflow'
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: 'lead-gen.company.qualify',
|
|
177
|
+
label: 'Qualify companies',
|
|
178
|
+
description: 'Score and filter companies against the ICP rubric.',
|
|
179
|
+
resourceId: 'lgn-03-company-qualification-workflow'
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: 'lead-gen.company.dtc-subscription-qualify',
|
|
183
|
+
label: 'Qualify DTC subscription fit',
|
|
184
|
+
description: 'Classify subscription potential and consumable-product fit for DTC brands.',
|
|
185
|
+
resourceId: 'lgn-03b-dtc-subscription-score-workflow'
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 'lead-gen.contact.apollo-decision-maker-enrich',
|
|
189
|
+
label: 'Enrich decision-makers',
|
|
190
|
+
description: 'Find and enrich qualified contacts at qualified companies via Apollo.',
|
|
191
|
+
resourceId: 'lgn-04b-apollo-decision-maker-enrich-workflow'
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: 'lead-gen.contact.personalize',
|
|
195
|
+
label: 'Personalize outreach',
|
|
196
|
+
description: 'Generate personalized opening lines for each contact.',
|
|
197
|
+
resourceId: 'ist-personalization-workflow'
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'lead-gen.review.outreach-ready',
|
|
201
|
+
label: 'Upload to outreach',
|
|
202
|
+
description: 'Upload approved contacts to the outreach sequence after QC review.',
|
|
203
|
+
resourceId: 'ist-upload-contacts-workflow'
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: 'lead-gen.export.list',
|
|
207
|
+
label: 'Export lead list',
|
|
208
|
+
description: 'Export approved leads as a downloadable lead list.',
|
|
209
|
+
resourceId: 'lgn-06-export-list-workflow'
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
id: 'lead-gen.company.cleanup',
|
|
213
|
+
label: 'Clean up companies',
|
|
214
|
+
description: 'Remove disqualified or duplicate companies from the list.',
|
|
215
|
+
resourceId: 'lgn-company-cleanup-workflow'
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
export function findCapabilityById(id: string): Capability | undefined {
|
|
220
|
+
return CAPABILITY_REGISTRY.find((c) => c.id === id)
|
|
221
|
+
}
|
|
49
222
|
|
|
50
223
|
export const PROSPECTING_STEPS = {
|
|
51
224
|
localServices: {
|
|
@@ -144,7 +317,46 @@ export const PROSPECTING_STEPS = {
|
|
|
144
317
|
dependencyMode: 'per-record-eligibility',
|
|
145
318
|
capabilityKey: 'lead-gen.company.apollo-import',
|
|
146
319
|
defaultBatchSize: 250,
|
|
147
|
-
maxBatchSize: 1000
|
|
320
|
+
maxBatchSize: 1000,
|
|
321
|
+
recordColumns: DTC_RECORD_COLUMNS.populated,
|
|
322
|
+
credentialRequirements: [
|
|
323
|
+
{
|
|
324
|
+
key: 'apollo',
|
|
325
|
+
provider: 'apollo',
|
|
326
|
+
credentialType: 'api-key-secret',
|
|
327
|
+
label: 'Apollo API key',
|
|
328
|
+
required: true,
|
|
329
|
+
selectionMode: 'single',
|
|
330
|
+
inputPath: 'credential'
|
|
331
|
+
}
|
|
332
|
+
]
|
|
333
|
+
},
|
|
334
|
+
apifyCrawl: {
|
|
335
|
+
id: 'apify-crawl',
|
|
336
|
+
label: 'Websites crawled',
|
|
337
|
+
description:
|
|
338
|
+
'Crawl company websites via Apify and store raw page markdown in enrichmentData.websiteCrawl.pages for downstream LLM analysis. Overwrites the synthetic seed Apollo Import wrote with real page content.',
|
|
339
|
+
primaryEntity: 'company',
|
|
340
|
+
outputs: ['company'],
|
|
341
|
+
stageKey: 'crawled',
|
|
342
|
+
dependsOn: ['import-apollo-search'],
|
|
343
|
+
dependencyMode: 'per-record-eligibility',
|
|
344
|
+
capabilityKey: 'lead-gen.company.apify-crawl',
|
|
345
|
+
defaultBatchSize: 50,
|
|
346
|
+
maxBatchSize: 100,
|
|
347
|
+
recordColumns: DTC_RECORD_COLUMNS.crawled,
|
|
348
|
+
credentialRequirements: [
|
|
349
|
+
{
|
|
350
|
+
key: 'apify',
|
|
351
|
+
provider: 'apify',
|
|
352
|
+
credentialType: 'api-key-secret',
|
|
353
|
+
label: 'Apify API token',
|
|
354
|
+
required: true,
|
|
355
|
+
selectionMode: 'single',
|
|
356
|
+
inputPath: 'credential',
|
|
357
|
+
verifyOnRun: true
|
|
358
|
+
}
|
|
359
|
+
]
|
|
148
360
|
},
|
|
149
361
|
analyzeWebsites: {
|
|
150
362
|
id: 'analyze-websites',
|
|
@@ -153,11 +365,12 @@ export const PROSPECTING_STEPS = {
|
|
|
153
365
|
primaryEntity: 'company',
|
|
154
366
|
outputs: ['company'],
|
|
155
367
|
stageKey: 'extracted',
|
|
156
|
-
dependsOn: ['
|
|
368
|
+
dependsOn: ['apify-crawl'],
|
|
157
369
|
dependencyMode: 'per-record-eligibility',
|
|
158
370
|
capabilityKey: 'lead-gen.company.website-extract',
|
|
159
371
|
defaultBatchSize: 50,
|
|
160
|
-
maxBatchSize: 100
|
|
372
|
+
maxBatchSize: 100,
|
|
373
|
+
recordColumns: DTC_RECORD_COLUMNS.extracted
|
|
161
374
|
},
|
|
162
375
|
scoreDtcFit: {
|
|
163
376
|
id: 'score-dtc-fit',
|
|
@@ -170,7 +383,8 @@ export const PROSPECTING_STEPS = {
|
|
|
170
383
|
dependencyMode: 'per-record-eligibility',
|
|
171
384
|
capabilityKey: 'lead-gen.company.dtc-subscription-qualify',
|
|
172
385
|
defaultBatchSize: 100,
|
|
173
|
-
maxBatchSize: 250
|
|
386
|
+
maxBatchSize: 250,
|
|
387
|
+
recordColumns: DTC_RECORD_COLUMNS.qualified
|
|
174
388
|
},
|
|
175
389
|
enrichDecisionMakers: {
|
|
176
390
|
id: 'enrich-decision-makers',
|
|
@@ -180,37 +394,53 @@ export const PROSPECTING_STEPS = {
|
|
|
180
394
|
primaryEntity: 'company',
|
|
181
395
|
outputs: ['contact'],
|
|
182
396
|
stageKey: 'decision-makers-enriched',
|
|
397
|
+
recordEntity: 'contact',
|
|
183
398
|
dependsOn: ['score-dtc-fit'],
|
|
184
399
|
dependencyMode: 'per-record-eligibility',
|
|
185
400
|
capabilityKey: 'lead-gen.contact.apollo-decision-maker-enrich',
|
|
186
401
|
defaultBatchSize: 100,
|
|
187
|
-
maxBatchSize: 250
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
maxBatchSize: 500
|
|
402
|
+
maxBatchSize: 250,
|
|
403
|
+
recordColumns: DTC_RECORD_COLUMNS.decisionMakers,
|
|
404
|
+
credentialRequirements: [
|
|
405
|
+
{
|
|
406
|
+
key: 'apollo',
|
|
407
|
+
provider: 'apollo',
|
|
408
|
+
credentialType: 'api-key-secret',
|
|
409
|
+
label: 'Apollo API key',
|
|
410
|
+
required: true,
|
|
411
|
+
selectionMode: 'single',
|
|
412
|
+
inputPath: 'credential'
|
|
413
|
+
}
|
|
414
|
+
]
|
|
201
415
|
},
|
|
202
416
|
reviewAndExport: {
|
|
203
417
|
id: 'review-and-export',
|
|
204
418
|
label: 'Reviewed and exported',
|
|
205
|
-
description:
|
|
419
|
+
description:
|
|
420
|
+
'Operator QC approves or rejects qualified companies, then approved records are exported as a lead list with unverified emails.',
|
|
206
421
|
primaryEntity: 'company',
|
|
207
422
|
outputs: ['export'],
|
|
208
423
|
stageKey: 'uploaded',
|
|
209
|
-
|
|
424
|
+
recordsStageKey: 'uploaded',
|
|
425
|
+
recordSourceStageKey: 'qualified',
|
|
426
|
+
dependsOn: ['enrich-decision-makers'],
|
|
210
427
|
dependencyMode: 'per-record-eligibility',
|
|
211
428
|
capabilityKey: 'lead-gen.export.list',
|
|
212
429
|
defaultBatchSize: 100,
|
|
213
|
-
maxBatchSize: 250
|
|
430
|
+
maxBatchSize: 250,
|
|
431
|
+
recordColumns: DTC_RECORD_COLUMNS.uploaded,
|
|
432
|
+
credentialRequirements: [
|
|
433
|
+
{
|
|
434
|
+
key: 'clickup',
|
|
435
|
+
provider: 'clickup',
|
|
436
|
+
credentialType: 'api-key-secret',
|
|
437
|
+
label: 'ClickUp API token',
|
|
438
|
+
required: true,
|
|
439
|
+
selectionMode: 'single',
|
|
440
|
+
inputPath: 'clickupCredential',
|
|
441
|
+
verifyOnRun: true
|
|
442
|
+
}
|
|
443
|
+
]
|
|
214
444
|
}
|
|
215
445
|
}
|
|
216
446
|
} as const satisfies Record<TemplateName, Record<StepName, ListBuilderStep>>
|
|
@@ -234,9 +464,11 @@ function toProspectingLifecycleStage(stage: LeadGenStageCatalogEntry): z.infer<t
|
|
|
234
464
|
}
|
|
235
465
|
}
|
|
236
466
|
|
|
237
|
-
function leadGenStagesForEntity(
|
|
467
|
+
function leadGenStagesForEntity(
|
|
468
|
+
entity: LeadGenStageCatalogEntry['entity']
|
|
469
|
+
): z.infer<typeof ProspectingLifecycleStageSchema>[] {
|
|
238
470
|
return Object.values(LEAD_GEN_STAGE_CATALOG)
|
|
239
|
-
.filter((stage) => stage.entity === entity)
|
|
471
|
+
.filter((stage) => stage.entity === entity || stage.additionalEntities?.includes(entity))
|
|
240
472
|
.sort((a, b) => a.order - b.order)
|
|
241
473
|
.map(toProspectingLifecycleStage)
|
|
242
474
|
}
|
|
@@ -271,10 +503,10 @@ export const DEFAULT_ORGANIZATION_MODEL_PROSPECTING: z.infer<typeof Organization
|
|
|
271
503
|
'Prospecting pipeline for DTC subscription or subscription-ready brands where Apollo is the source and contact-enrichment layer, Elevasis handles company research and fit scoring, and approved leads export as an approved lead list.',
|
|
272
504
|
steps: [
|
|
273
505
|
PROSPECTING_STEPS.dtcApolloClickup.importApolloSearch,
|
|
506
|
+
PROSPECTING_STEPS.dtcApolloClickup.apifyCrawl,
|
|
274
507
|
PROSPECTING_STEPS.dtcApolloClickup.analyzeWebsites,
|
|
275
508
|
PROSPECTING_STEPS.dtcApolloClickup.scoreDtcFit,
|
|
276
509
|
PROSPECTING_STEPS.dtcApolloClickup.enrichDecisionMakers,
|
|
277
|
-
PROSPECTING_STEPS.dtcApolloClickup.verifyEmails,
|
|
278
510
|
PROSPECTING_STEPS.dtcApolloClickup.reviewAndExport
|
|
279
511
|
]
|
|
280
512
|
}
|
|
@@ -113,6 +113,7 @@ export const DEFAULT_ORGANIZATION_MODEL_SALES: z.infer<typeof OrganizationModelS
|
|
|
113
113
|
// - interested (instantly-reply-handler.ts → contacts, initial reply transition)
|
|
114
114
|
// prospecting/:
|
|
115
115
|
// - populated (apify-acquire.ts, apify-scrape.ts → companies)
|
|
116
|
+
// - crawled (apify-website-crawl.ts → companies)
|
|
116
117
|
// - extracted (website-extract.ts → companies)
|
|
117
118
|
// - discovered (email-discovery.ts, anymailfinder-enrich.ts → contacts)
|
|
118
119
|
// - verified (email-verification.ts → contacts)
|
|
@@ -135,6 +136,8 @@ export interface StatefulStageDefinition {
|
|
|
135
136
|
/** Matches stage_key values written by workflow steps. */
|
|
136
137
|
stageKey: string
|
|
137
138
|
label: string
|
|
139
|
+
/** UI color token. Consumers may map this to their design system. */
|
|
140
|
+
color?: string
|
|
138
141
|
states: StatefulStateDefinition[]
|
|
139
142
|
}
|
|
140
143
|
|
|
@@ -251,6 +254,7 @@ export const CRM_PIPELINE_DEFINITION: StatefulPipelineDefinition = {
|
|
|
251
254
|
{
|
|
252
255
|
stageKey: 'interested',
|
|
253
256
|
label: 'Interested',
|
|
257
|
+
color: 'blue',
|
|
254
258
|
states: [
|
|
255
259
|
CRM_DISCOVERY_REPLIED_STATE,
|
|
256
260
|
CRM_DISCOVERY_LINK_SENT_STATE,
|
|
@@ -262,11 +266,11 @@ export const CRM_PIPELINE_DEFINITION: StatefulPipelineDefinition = {
|
|
|
262
266
|
CRM_FOLLOWUP_3_SENT_STATE
|
|
263
267
|
]
|
|
264
268
|
},
|
|
265
|
-
{ stageKey: 'proposal', label: 'Proposal', states: [] },
|
|
266
|
-
{ stageKey: 'closing', label: 'Closing', states: [] },
|
|
267
|
-
{ stageKey: 'closed_won', label: 'Closed Won', states: [] },
|
|
268
|
-
{ stageKey: 'closed_lost', label: 'Closed Lost', states: [] },
|
|
269
|
-
{ stageKey: 'nurturing', label: 'Nurturing', states: [] }
|
|
269
|
+
{ stageKey: 'proposal', label: 'Proposal', color: 'yellow', states: [] },
|
|
270
|
+
{ stageKey: 'closing', label: 'Closing', color: 'orange', states: [] },
|
|
271
|
+
{ stageKey: 'closed_won', label: 'Closed Won', color: 'green', states: [] },
|
|
272
|
+
{ stageKey: 'closed_lost', label: 'Closed Lost', color: 'red', states: [] },
|
|
273
|
+
{ stageKey: 'nurturing', label: 'Nurturing', color: 'grape', states: [] }
|
|
270
274
|
]
|
|
271
275
|
}
|
|
272
276
|
|
|
@@ -411,7 +415,7 @@ export const ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE: StatefulPipelineDefinition =
|
|
|
411
415
|
{
|
|
412
416
|
stageKey: 'outreach',
|
|
413
417
|
label: 'Outreach',
|
|
414
|
-
states: [PENDING_STATE]
|
|
418
|
+
states: [PENDING_STATE, { stateKey: 'uploaded', label: 'Uploaded' }]
|
|
415
419
|
},
|
|
416
420
|
{
|
|
417
421
|
stageKey: 'prospecting',
|
|
@@ -469,6 +473,15 @@ export interface LeadGenStageCatalogEntry {
|
|
|
469
473
|
order: number
|
|
470
474
|
/** Which entity's processing_state jsonb carries this stage status. */
|
|
471
475
|
entity: 'company' | 'contact'
|
|
476
|
+
/** Additional entities allowed to write/read this processing_state key. */
|
|
477
|
+
additionalEntities?: Array<'company' | 'contact'>
|
|
478
|
+
/**
|
|
479
|
+
* Optional read-side override for Records views when a company-scoped step
|
|
480
|
+
* produces records on a different entity.
|
|
481
|
+
*/
|
|
482
|
+
recordEntity?: 'company' | 'contact'
|
|
483
|
+
/** Stage key to read from recordEntity.processing_state for Records views. */
|
|
484
|
+
recordStageKey?: string
|
|
472
485
|
}
|
|
473
486
|
|
|
474
487
|
/**
|
|
@@ -493,6 +506,14 @@ export const LEAD_GEN_STAGE_CATALOG: Record<string, LeadGenStageCatalogEntry> =
|
|
|
493
506
|
order: 2,
|
|
494
507
|
entity: 'company'
|
|
495
508
|
},
|
|
509
|
+
crawled: {
|
|
510
|
+
key: 'crawled',
|
|
511
|
+
label: 'Websites crawled',
|
|
512
|
+
description:
|
|
513
|
+
'Company websites have been crawled (e.g. via Apify) and raw page content stored for downstream LLM analysis.',
|
|
514
|
+
order: 2.5,
|
|
515
|
+
entity: 'company'
|
|
516
|
+
},
|
|
496
517
|
extracted: {
|
|
497
518
|
key: 'extracted',
|
|
498
519
|
label: 'Websites analyzed',
|
|
@@ -512,7 +533,9 @@ export const LEAD_GEN_STAGE_CATALOG: Record<string, LeadGenStageCatalogEntry> =
|
|
|
512
533
|
label: 'Decision-makers found',
|
|
513
534
|
description: 'Decision-maker contacts discovered and attached to a qualified company.',
|
|
514
535
|
order: 6,
|
|
515
|
-
entity: 'company'
|
|
536
|
+
entity: 'company',
|
|
537
|
+
recordEntity: 'contact',
|
|
538
|
+
recordStageKey: 'discovered'
|
|
516
539
|
},
|
|
517
540
|
|
|
518
541
|
// Prospecting — contact discovery
|
|
@@ -553,7 +576,8 @@ export const LEAD_GEN_STAGE_CATALOG: Record<string, LeadGenStageCatalogEntry> =
|
|
|
553
576
|
label: 'Reviewed and exported',
|
|
554
577
|
description: 'Approved records have been reviewed and exported for handoff.',
|
|
555
578
|
order: 10,
|
|
556
|
-
entity: '
|
|
579
|
+
entity: 'company',
|
|
580
|
+
additionalEntities: ['contact']
|
|
557
581
|
},
|
|
558
582
|
interested: {
|
|
559
583
|
key: 'interested',
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
OrganizationGraphNode,
|
|
8
8
|
OrganizationGraphNodeKind
|
|
9
9
|
} from './types'
|
|
10
|
+
import { CAPABILITY_REGISTRY } from '../domains/prospecting'
|
|
10
11
|
|
|
11
12
|
function nodeId(kind: OrganizationGraphNodeKind, sourceId?: string): string {
|
|
12
13
|
return kind === 'organization' ? 'organization-model' : `${kind}:${sourceId ?? ''}`
|
|
@@ -187,6 +188,79 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
const allStages = [
|
|
192
|
+
...organizationModel.prospecting.companyStages,
|
|
193
|
+
...organizationModel.prospecting.contactStages
|
|
194
|
+
].sort((a, b) => a.order - b.order || a.id.localeCompare(b.id))
|
|
195
|
+
|
|
196
|
+
for (const stage of allStages) {
|
|
197
|
+
const id = nodeId('stage', stage.id)
|
|
198
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
199
|
+
id,
|
|
200
|
+
kind: 'stage',
|
|
201
|
+
label: stage.label,
|
|
202
|
+
sourceId: stage.id,
|
|
203
|
+
...(stage.description ? { description: stage.description } : {}),
|
|
204
|
+
...(stage.icon ? { icon: stage.icon } : {})
|
|
205
|
+
})
|
|
206
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
207
|
+
id: edgeId('contains', organizationNode.id, id),
|
|
208
|
+
kind: 'contains',
|
|
209
|
+
sourceId: organizationNode.id,
|
|
210
|
+
targetId: id
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const cap of [...CAPABILITY_REGISTRY].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
215
|
+
const id = nodeId('capability', cap.id)
|
|
216
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
217
|
+
id,
|
|
218
|
+
kind: 'capability',
|
|
219
|
+
label: cap.label,
|
|
220
|
+
sourceId: cap.id,
|
|
221
|
+
description: cap.description
|
|
222
|
+
})
|
|
223
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
224
|
+
id: edgeId('contains', organizationNode.id, id),
|
|
225
|
+
kind: 'contains',
|
|
226
|
+
sourceId: organizationNode.id,
|
|
227
|
+
targetId: id
|
|
228
|
+
})
|
|
229
|
+
const resourceNode = ensureResourceNode(nodes, nodeIds, resourceNodesById, cap.resourceId)
|
|
230
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
231
|
+
id: edgeId('maps_to', id, resourceNode.id),
|
|
232
|
+
kind: 'maps_to',
|
|
233
|
+
sourceId: id,
|
|
234
|
+
targetId: resourceNode.id
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for (const template of [...organizationModel.prospecting.buildTemplates].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
239
|
+
const stepById = new Map(template.steps.map((s) => [s.id, s]))
|
|
240
|
+
for (const step of [...template.steps].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
241
|
+
const stageNodeId = nodeId('stage', step.stageKey)
|
|
242
|
+
const capNodeId = nodeId('capability', step.capabilityKey)
|
|
243
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
244
|
+
id: edgeId('uses', stageNodeId, capNodeId, step.id),
|
|
245
|
+
kind: 'uses',
|
|
246
|
+
sourceId: stageNodeId,
|
|
247
|
+
targetId: capNodeId
|
|
248
|
+
})
|
|
249
|
+
for (const depId of step.dependsOn ?? []) {
|
|
250
|
+
const depStep = stepById.get(depId)
|
|
251
|
+
if (depStep) {
|
|
252
|
+
const depStageNodeId = nodeId('stage', depStep.stageKey)
|
|
253
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
254
|
+
id: edgeId('references', stageNodeId, depStageNodeId, step.id),
|
|
255
|
+
kind: 'references',
|
|
256
|
+
sourceId: stageNodeId,
|
|
257
|
+
targetId: depStageNodeId
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
190
264
|
if (commandViewData) {
|
|
191
265
|
const commandViewResources = collectCommandViewResources(commandViewData).sort((a, b) =>
|
|
192
266
|
a.resourceId.localeCompare(b.resourceId)
|