@elevasis/core 0.24.1 → 0.26.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 (82) hide show
  1. package/dist/index.d.ts +239 -86
  2. package/dist/index.js +474 -1346
  3. package/dist/knowledge/index.d.ts +57 -39
  4. package/dist/knowledge/index.js +1 -1
  5. package/dist/organization-model/index.d.ts +239 -86
  6. package/dist/organization-model/index.js +474 -1346
  7. package/dist/test-utils/index.d.ts +24 -31
  8. package/dist/test-utils/index.js +76 -1238
  9. package/package.json +1 -1
  10. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +108 -96
  11. package/src/business/acquisition/api-schemas.test.ts +70 -77
  12. package/src/business/acquisition/api-schemas.ts +21 -42
  13. package/src/business/acquisition/derive-actions.test.ts +11 -21
  14. package/src/business/acquisition/derive-actions.ts +61 -14
  15. package/src/business/acquisition/ontology-validation.ts +4 -4
  16. package/src/business/acquisition/types.ts +7 -8
  17. package/src/execution/engine/llm/adapters/__tests__/openrouter.integration.test.ts +10 -10
  18. package/src/knowledge/__tests__/queries.test.ts +960 -546
  19. package/src/knowledge/format.ts +322 -100
  20. package/src/knowledge/index.ts +18 -5
  21. package/src/knowledge/queries.ts +1004 -240
  22. package/src/organization-model/__tests__/content-kinds-registry.test.ts +35 -210
  23. package/src/organization-model/__tests__/defaults.test.ts +4 -4
  24. package/src/organization-model/__tests__/deprecate-helpers.test.ts +71 -0
  25. package/src/organization-model/__tests__/domains/actions.test.ts +12 -36
  26. package/src/organization-model/__tests__/domains/offerings.test.ts +13 -6
  27. package/src/organization-model/__tests__/domains/resources.test.ts +497 -350
  28. package/src/organization-model/__tests__/domains/systems.test.ts +6 -7
  29. package/src/organization-model/__tests__/flatten-additive-merge.test.ts +68 -80
  30. package/src/organization-model/__tests__/foundation.test.ts +81 -14
  31. package/src/organization-model/__tests__/graph.test.ts +662 -694
  32. package/src/organization-model/__tests__/knowledge.test.ts +31 -17
  33. package/src/organization-model/__tests__/lookup-helpers.test.ts +128 -438
  34. package/src/organization-model/__tests__/migration-helpers.test.ts +362 -591
  35. package/src/organization-model/__tests__/prospecting-ssot.test.ts +68 -103
  36. package/src/organization-model/__tests__/published-zero-leak.test.ts +17 -0
  37. package/src/organization-model/__tests__/recursive-system-schema.test.ts +159 -532
  38. package/src/organization-model/__tests__/resolve.test.ts +88 -49
  39. package/src/organization-model/__tests__/scaffolders.test.ts +93 -0
  40. package/src/organization-model/__tests__/schema.test.ts +65 -56
  41. package/src/organization-model/catalogs/lead-gen.ts +0 -103
  42. package/src/organization-model/defaults.ts +17 -702
  43. package/src/organization-model/domains/actions.ts +116 -333
  44. package/src/organization-model/domains/knowledge.ts +15 -7
  45. package/src/organization-model/domains/projects.ts +4 -4
  46. package/src/organization-model/domains/prospecting.ts +405 -395
  47. package/src/organization-model/domains/resources.ts +206 -135
  48. package/src/organization-model/domains/sales.ts +5 -5
  49. package/src/organization-model/domains/systems.ts +8 -23
  50. package/src/organization-model/graph/build.ts +223 -294
  51. package/src/organization-model/graph/schema.ts +2 -3
  52. package/src/organization-model/graph/types.ts +12 -14
  53. package/src/organization-model/helpers.ts +120 -141
  54. package/src/organization-model/icons.ts +1 -0
  55. package/src/organization-model/index.ts +107 -126
  56. package/src/organization-model/migration-helpers.ts +211 -249
  57. package/src/organization-model/ontology.ts +0 -60
  58. package/src/organization-model/organization-graph.mdx +4 -5
  59. package/src/organization-model/organization-model.mdx +1 -1
  60. package/src/organization-model/published.ts +251 -228
  61. package/src/organization-model/resolve.ts +4 -5
  62. package/src/organization-model/scaffolders/helpers.ts +84 -0
  63. package/src/organization-model/scaffolders/index.ts +19 -0
  64. package/src/organization-model/scaffolders/scaffoldKnowledgeNode.ts +48 -0
  65. package/src/organization-model/scaffolders/scaffoldOntologyRecord.ts +38 -0
  66. package/src/organization-model/scaffolders/scaffoldResource.ts +59 -0
  67. package/src/organization-model/scaffolders/scaffoldSystem.ts +110 -0
  68. package/src/organization-model/scaffolders/types.ts +81 -0
  69. package/src/organization-model/schema.ts +610 -704
  70. package/src/organization-model/types.ts +167 -161
  71. package/src/platform/constants/versions.ts +1 -1
  72. package/src/platform/registry/__tests__/validation.test.ts +23 -0
  73. package/src/platform/registry/validation.ts +13 -2
  74. package/src/reference/_generated/contracts.md +108 -96
  75. package/src/reference/glossary.md +71 -69
  76. package/src/organization-model/content-kinds/config.ts +0 -36
  77. package/src/organization-model/content-kinds/index.ts +0 -78
  78. package/src/organization-model/content-kinds/pipeline.ts +0 -68
  79. package/src/organization-model/content-kinds/registry.ts +0 -44
  80. package/src/organization-model/content-kinds/status.ts +0 -71
  81. package/src/organization-model/content-kinds/template.ts +0 -83
  82. package/src/organization-model/content-kinds/types.ts +0 -117
@@ -1,395 +1,405 @@
1
- import { z } from 'zod'
2
- import { findOrganizationActionById, LEAD_GEN_ACTION_ENTRIES, type Action as TopLevelAction } from './actions'
3
- import { DisplayMetadataSchema, ModelIdSchema } from './shared'
4
-
5
- export const ProspectingLifecycleStageSchema = DisplayMetadataSchema.extend({
6
- id: ModelIdSchema,
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()
37
- })
38
-
39
- export const ProspectingBuildTemplateStepSchema = DisplayMetadataSchema.extend({
40
- id: ModelIdSchema,
41
- primaryEntity: z.enum(['company', 'contact']),
42
- outputs: z.array(z.enum(['company', 'contact', 'export'])).min(1),
43
- stageKey: ModelIdSchema,
44
- recordEntity: z.enum(['company', 'contact']).optional(),
45
- recordsStageKey: ModelIdSchema.optional(),
46
- recordSourceStageKey: ModelIdSchema.optional(),
47
- dependsOn: z.array(ModelIdSchema).optional(),
48
- dependencyMode: z.literal('per-record-eligibility'),
49
- actionKey: ModelIdSchema,
50
- defaultBatchSize: z.number().int().positive(),
51
- maxBatchSize: z.number().int().positive(),
52
- recordColumns: RecordColumnsConfigSchema.optional(),
53
- credentialRequirements: z.array(CredentialRequirementSchema).optional()
54
- }).refine((step) => step.defaultBatchSize <= step.maxBatchSize, {
55
- message: 'defaultBatchSize must be less than or equal to maxBatchSize',
56
- path: ['defaultBatchSize']
57
- })
58
-
59
- export const ProspectingBuildTemplateSchema = DisplayMetadataSchema.extend({
60
- id: ModelIdSchema,
61
- steps: z.array(ProspectingBuildTemplateStepSchema).min(1)
62
- })
63
-
64
- export type ListBuilderStep = z.infer<typeof ProspectingBuildTemplateStepSchema>
65
- export type RecordColumnConfig = z.infer<typeof RecordColumnConfigSchema>
66
- export type CredentialRequirement = z.infer<typeof CredentialRequirementSchema>
67
- export type TemplateName = 'localServices' | 'dtcApolloClickup'
68
- export type StepName = string
69
-
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
- {
95
- key: 'automation-gaps',
96
- label: 'Automation gaps',
97
- path: 'company.enrichmentData.websiteCrawl.automationGaps',
98
- renderType: 'json'
99
- },
100
- {
101
- key: 'contact-count',
102
- label: 'Contacts',
103
- path: 'company.enrichmentData.websiteCrawl.emailCount',
104
- renderType: 'count'
105
- }
106
- ]
107
- },
108
- qualified: {
109
- company: [
110
- { key: 'name', label: 'Company', path: 'company.name' },
111
- { key: 'domain', label: 'Domain', path: 'company.domain' },
112
- { key: 'score', label: 'Score', path: 'company.qualificationScore', renderType: 'badge', badgeColor: 'green' },
113
- { key: 'signals', label: 'Signals', path: 'company.qualificationSignals', renderType: 'json' },
114
- {
115
- key: 'disqualified-reason',
116
- label: 'Disqualified reason',
117
- path: 'processingState.qualified.data.disqualifiedReason'
118
- }
119
- ]
120
- },
121
- decisionMakers: {
122
- contact: [
123
- { key: 'name', label: 'Name', path: 'contact.name' },
124
- { key: 'title', label: 'Title', path: 'contact.title' },
125
- { key: 'email', label: 'Email', path: 'contact.email' },
126
- { key: 'linkedin', label: 'LinkedIn', path: 'contact.linkedinUrl' },
127
- {
128
- key: 'priority-score',
129
- label: 'Priority',
130
- path: 'contact.enrichmentData.apollo.priorityScore',
131
- renderType: 'badge'
132
- }
133
- ]
134
- },
135
- uploaded: {
136
- company: [
137
- { key: 'name', label: 'Company', path: 'company.name' },
138
- { key: 'domain', label: 'Domain', path: 'company.domain' },
139
- {
140
- key: 'contacts',
141
- label: 'Contacts',
142
- path: 'company.enrichmentData.approvedLeadListExport.contacts',
143
- renderType: 'json'
144
- },
145
- { key: 'score', label: 'Score', path: 'company.qualificationScore', renderType: 'badge', badgeColor: 'green' },
146
- {
147
- key: 'approval',
148
- label: 'Approval',
149
- path: 'company.enrichmentData.approvedLeadListExport.approvalStatus',
150
- renderType: 'badge'
151
- }
152
- ]
153
- }
154
- } as const satisfies Record<string, z.infer<typeof RecordColumnsConfigSchema>>
155
-
156
- export type ActionRegistry = TopLevelAction[]
157
-
158
- export const ACTION_REGISTRY: ActionRegistry = Object.values(LEAD_GEN_ACTION_ENTRIES)
159
-
160
- export function findActionById(id: string): TopLevelAction | undefined {
161
- return findOrganizationActionById(id)
162
- }
163
-
164
- export const PROSPECTING_STEPS = {
165
- localServices: {
166
- sourceCompanies: {
167
- id: 'source-companies',
168
- label: 'Companies found',
169
- primaryEntity: 'company',
170
- outputs: ['company'],
171
- stageKey: 'populated',
172
- dependencyMode: 'per-record-eligibility',
173
- actionKey: 'lead-gen.company.source',
174
- defaultBatchSize: 100,
175
- maxBatchSize: 250
176
- },
177
- analyzeWebsites: {
178
- id: 'analyze-websites',
179
- label: 'Websites analyzed',
180
- primaryEntity: 'company',
181
- outputs: ['company'],
182
- stageKey: 'extracted',
183
- dependsOn: ['source-companies'],
184
- dependencyMode: 'per-record-eligibility',
185
- actionKey: 'lead-gen.company.website-extract',
186
- defaultBatchSize: 50,
187
- maxBatchSize: 100
188
- },
189
- qualifyCompanies: {
190
- id: 'qualify-companies',
191
- label: 'Companies qualified',
192
- primaryEntity: 'company',
193
- outputs: ['company'],
194
- stageKey: 'qualified',
195
- dependsOn: ['analyze-websites'],
196
- dependencyMode: 'per-record-eligibility',
197
- actionKey: 'lead-gen.company.qualify',
198
- defaultBatchSize: 100,
199
- maxBatchSize: 250
200
- },
201
- findContacts: {
202
- id: 'find-contacts',
203
- label: 'Decision-makers found',
204
- primaryEntity: 'contact',
205
- outputs: ['contact'],
206
- stageKey: 'discovered',
207
- dependsOn: ['qualify-companies'],
208
- dependencyMode: 'per-record-eligibility',
209
- actionKey: 'lead-gen.contact.discover',
210
- defaultBatchSize: 50,
211
- maxBatchSize: 100
212
- },
213
- verifyEmails: {
214
- id: 'verify-emails',
215
- label: 'Emails verified',
216
- primaryEntity: 'contact',
217
- outputs: ['contact'],
218
- stageKey: 'verified',
219
- dependsOn: ['find-contacts'],
220
- dependencyMode: 'per-record-eligibility',
221
- actionKey: 'lead-gen.contact.verify-email',
222
- defaultBatchSize: 100,
223
- maxBatchSize: 500
224
- },
225
- personalize: {
226
- id: 'personalize',
227
- label: 'Personalize',
228
- primaryEntity: 'contact',
229
- outputs: ['contact'],
230
- stageKey: 'personalized',
231
- dependsOn: ['verify-emails'],
232
- dependencyMode: 'per-record-eligibility',
233
- actionKey: 'lead-gen.contact.personalize',
234
- defaultBatchSize: 25,
235
- maxBatchSize: 100
236
- },
237
- review: {
238
- id: 'review',
239
- label: 'Reviewed and exported',
240
- primaryEntity: 'contact',
241
- outputs: ['export'],
242
- stageKey: 'uploaded',
243
- dependsOn: ['personalize'],
244
- dependencyMode: 'per-record-eligibility',
245
- actionKey: 'lead-gen.review.outreach-ready',
246
- defaultBatchSize: 25,
247
- maxBatchSize: 100
248
- }
249
- },
250
- dtcApolloClickup: {
251
- importApolloSearch: {
252
- id: 'import-apollo-search',
253
- label: 'Companies found',
254
- description: 'Pull companies and seed contact data from a predefined Apollo search or list.',
255
- primaryEntity: 'company',
256
- outputs: ['company', 'contact'],
257
- stageKey: 'populated',
258
- dependencyMode: 'per-record-eligibility',
259
- actionKey: 'lead-gen.company.apollo-import',
260
- defaultBatchSize: 250,
261
- maxBatchSize: 1000,
262
- recordColumns: DTC_RECORD_COLUMNS.populated,
263
- credentialRequirements: [
264
- {
265
- key: 'apollo',
266
- provider: 'apollo',
267
- credentialType: 'api-key-secret',
268
- label: 'Apollo API key',
269
- required: true,
270
- selectionMode: 'single',
271
- inputPath: 'credential'
272
- }
273
- ]
274
- },
275
- apifyCrawl: {
276
- id: 'apify-crawl',
277
- label: 'Websites crawled',
278
- description:
279
- '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.',
280
- primaryEntity: 'company',
281
- outputs: ['company'],
282
- stageKey: 'crawled',
283
- dependsOn: ['import-apollo-search'],
284
- dependencyMode: 'per-record-eligibility',
285
- actionKey: 'lead-gen.company.apify-crawl',
286
- defaultBatchSize: 50,
287
- maxBatchSize: 100,
288
- recordColumns: DTC_RECORD_COLUMNS.crawled,
289
- credentialRequirements: [
290
- {
291
- key: 'apify',
292
- provider: 'apify',
293
- credentialType: 'api-key-secret',
294
- label: 'Apify API token',
295
- required: true,
296
- selectionMode: 'single',
297
- inputPath: 'credential',
298
- verifyOnRun: true
299
- }
300
- ]
301
- },
302
- analyzeWebsites: {
303
- id: 'analyze-websites',
304
- label: 'Websites analyzed',
305
- description: 'Extract subscription, product, retention, and tech-stack signals from each brand website.',
306
- primaryEntity: 'company',
307
- outputs: ['company'],
308
- stageKey: 'extracted',
309
- dependsOn: ['apify-crawl'],
310
- dependencyMode: 'per-record-eligibility',
311
- actionKey: 'lead-gen.company.website-extract',
312
- defaultBatchSize: 50,
313
- maxBatchSize: 100,
314
- recordColumns: DTC_RECORD_COLUMNS.extracted
315
- },
316
- scoreDtcFit: {
317
- id: 'score-dtc-fit',
318
- label: 'Companies qualified',
319
- description: 'Classify subscription potential, consumable-product fit, retention maturity, and disqualifiers.',
320
- primaryEntity: 'company',
321
- outputs: ['company'],
322
- stageKey: 'qualified',
323
- dependsOn: ['analyze-websites'],
324
- dependencyMode: 'per-record-eligibility',
325
- actionKey: 'lead-gen.company.dtc-subscription-qualify',
326
- defaultBatchSize: 100,
327
- maxBatchSize: 250,
328
- recordColumns: DTC_RECORD_COLUMNS.qualified
329
- },
330
- enrichDecisionMakers: {
331
- id: 'enrich-decision-makers',
332
- label: 'Decision-makers found',
333
- description:
334
- 'Use Apollo to find qualified contacts at qualified companies - founders, retention leads, lifecycle leads, and marketing owners.',
335
- primaryEntity: 'company',
336
- outputs: ['contact'],
337
- stageKey: 'decision-makers-enriched',
338
- recordEntity: 'contact',
339
- dependsOn: ['score-dtc-fit'],
340
- dependencyMode: 'per-record-eligibility',
341
- actionKey: 'lead-gen.contact.apollo-decision-maker-enrich',
342
- defaultBatchSize: 100,
343
- maxBatchSize: 250,
344
- recordColumns: DTC_RECORD_COLUMNS.decisionMakers,
345
- credentialRequirements: [
346
- {
347
- key: 'apollo',
348
- provider: 'apollo',
349
- credentialType: 'api-key-secret',
350
- label: 'Apollo API key',
351
- required: true,
352
- selectionMode: 'single',
353
- inputPath: 'credential'
354
- }
355
- ]
356
- },
357
- reviewAndExport: {
358
- id: 'review-and-export',
359
- label: 'Reviewed and exported',
360
- description:
361
- 'Operator QC approves or rejects qualified companies, then approved records are exported as a lead list with unverified emails.',
362
- primaryEntity: 'company',
363
- outputs: ['export'],
364
- stageKey: 'uploaded',
365
- recordsStageKey: 'uploaded',
366
- recordSourceStageKey: 'qualified',
367
- dependsOn: ['enrich-decision-makers'],
368
- dependencyMode: 'per-record-eligibility',
369
- actionKey: 'lead-gen.export.list',
370
- defaultBatchSize: 100,
371
- maxBatchSize: 250,
372
- recordColumns: DTC_RECORD_COLUMNS.uploaded,
373
- credentialRequirements: [
374
- {
375
- key: 'clickup',
376
- provider: 'clickup',
377
- credentialType: 'api-key-secret',
378
- label: 'ClickUp API token',
379
- required: true,
380
- selectionMode: 'single',
381
- inputPath: 'clickupCredential',
382
- verifyOnRun: true
383
- }
384
- ]
385
- }
386
- }
387
- } as const satisfies Record<TemplateName, Record<StepName, ListBuilderStep>>
388
-
389
- // Phase 4 cut: OrganizationModelProspectingSchema and DEFAULT_ORGANIZATION_MODEL_PROSPECTING removed.
390
- // Build-template/stage data moved into system.content as (schema:template), (schema:template-step),
391
- // and (schema:stage) content nodes. Use getAllBuildTemplates() / getAllProspectingStages()
392
- // from migration-helpers to read prospecting data portably.
393
- //
394
- // ProspectingBuildTemplateSchema, ProspectingBuildTemplateStepSchema, ProspectingLifecycleStageSchema,
395
- // PROSPECTING_STEPS, and ACTION_REGISTRY are retained for use by business logic and UI.
1
+ import { z } from 'zod'
2
+ import { findOrganizationActionById, type Action as TopLevelAction } from './actions'
3
+ import { DisplayMetadataSchema, ModelIdSchema } from './shared'
4
+
5
+ export const ProspectingLifecycleStageSchema = DisplayMetadataSchema.extend({
6
+ id: ModelIdSchema,
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()
37
+ })
38
+
39
+ export const ProspectingBuildTemplateStepSchema = DisplayMetadataSchema.extend({
40
+ id: ModelIdSchema,
41
+ primaryEntity: z.enum(['company', 'contact']),
42
+ outputs: z.array(z.enum(['company', 'contact', 'export'])).min(1),
43
+ stageKey: ModelIdSchema,
44
+ recordEntity: z.enum(['company', 'contact']).optional(),
45
+ recordsStageKey: ModelIdSchema.optional(),
46
+ recordSourceStageKey: ModelIdSchema.optional(),
47
+ dependsOn: z.array(ModelIdSchema).optional(),
48
+ dependencyMode: z.literal('per-record-eligibility'),
49
+ actionKey: ModelIdSchema,
50
+ defaultBatchSize: z.number().int().positive(),
51
+ maxBatchSize: z.number().int().positive(),
52
+ recordColumns: RecordColumnsConfigSchema.optional(),
53
+ credentialRequirements: z.array(CredentialRequirementSchema).optional()
54
+ }).refine((step) => step.defaultBatchSize <= step.maxBatchSize, {
55
+ message: 'defaultBatchSize must be less than or equal to maxBatchSize',
56
+ path: ['defaultBatchSize']
57
+ })
58
+
59
+ export const ProspectingBuildTemplateSchema = DisplayMetadataSchema.extend({
60
+ id: ModelIdSchema,
61
+ steps: z.array(ProspectingBuildTemplateStepSchema).min(1)
62
+ })
63
+
64
+ export type ListBuilderStep = z.infer<typeof ProspectingBuildTemplateStepSchema>
65
+ export type RecordColumnConfig = z.infer<typeof RecordColumnConfigSchema>
66
+ export type CredentialRequirement = z.infer<typeof CredentialRequirementSchema>
67
+ /**
68
+ * Generic template name type. Elevasis-specific template names
69
+ * ('localServices' | 'dtcApolloClickup') have been relocated to
70
+ * `@repo/elevasis-core/src/organization-model/actions.ts`.
71
+ */
72
+ export type TemplateName = string
73
+ export type StepName = string
74
+
75
+ const DTC_RECORD_COLUMNS = {
76
+ populated: {
77
+ company: [
78
+ { key: 'name', label: 'Company', path: 'company.name' },
79
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
80
+ { key: 'employee-count', label: 'Employees', path: 'company.numEmployees', renderType: 'count' },
81
+ { key: 'apollo-industry', label: 'Apollo industry', path: 'company.category' },
82
+ { key: 'location', label: 'Location', path: 'company.locationState' }
83
+ ]
84
+ },
85
+ crawled: {
86
+ company: [
87
+ { key: 'name', label: 'Company', path: 'company.name' },
88
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
89
+ { key: 'page-count', label: 'Pages', path: 'company.enrichmentData.websiteCrawl.pageCount', renderType: 'count' },
90
+ { key: 'crawl-status', label: 'Crawl status', path: 'processingState.crawled.status', renderType: 'badge' }
91
+ ]
92
+ },
93
+ extracted: {
94
+ company: [
95
+ { key: 'name', label: 'Company', path: 'company.name' },
96
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
97
+ { key: 'description', label: 'Description', path: 'company.enrichmentData.websiteCrawl.companyDescription' },
98
+ { key: 'services', label: 'Services', path: 'company.enrichmentData.websiteCrawl.services', renderType: 'json' },
99
+ {
100
+ key: 'automation-gaps',
101
+ label: 'Automation gaps',
102
+ path: 'company.enrichmentData.websiteCrawl.automationGaps',
103
+ renderType: 'json'
104
+ },
105
+ {
106
+ key: 'contact-count',
107
+ label: 'Contacts',
108
+ path: 'company.enrichmentData.websiteCrawl.emailCount',
109
+ renderType: 'count'
110
+ }
111
+ ]
112
+ },
113
+ qualified: {
114
+ company: [
115
+ { key: 'name', label: 'Company', path: 'company.name' },
116
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
117
+ { key: 'score', label: 'Score', path: 'company.qualificationScore', renderType: 'badge', badgeColor: 'green' },
118
+ { key: 'signals', label: 'Signals', path: 'company.qualificationSignals', renderType: 'json' },
119
+ {
120
+ key: 'disqualified-reason',
121
+ label: 'Disqualified reason',
122
+ path: 'processingState.qualified.data.disqualifiedReason'
123
+ }
124
+ ]
125
+ },
126
+ decisionMakers: {
127
+ contact: [
128
+ { key: 'name', label: 'Name', path: 'contact.name' },
129
+ { key: 'title', label: 'Title', path: 'contact.title' },
130
+ { key: 'email', label: 'Email', path: 'contact.email' },
131
+ { key: 'linkedin', label: 'LinkedIn', path: 'contact.linkedinUrl' },
132
+ {
133
+ key: 'priority-score',
134
+ label: 'Priority',
135
+ path: 'contact.enrichmentData.apollo.priorityScore',
136
+ renderType: 'badge'
137
+ }
138
+ ]
139
+ },
140
+ uploaded: {
141
+ company: [
142
+ { key: 'name', label: 'Company', path: 'company.name' },
143
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
144
+ {
145
+ key: 'contacts',
146
+ label: 'Contacts',
147
+ path: 'company.enrichmentData.approvedLeadListExport.contacts',
148
+ renderType: 'json'
149
+ },
150
+ { key: 'score', label: 'Score', path: 'company.qualificationScore', renderType: 'badge', badgeColor: 'green' },
151
+ {
152
+ key: 'approval',
153
+ label: 'Approval',
154
+ path: 'company.enrichmentData.approvedLeadListExport.approvalStatus',
155
+ renderType: 'badge'
156
+ }
157
+ ]
158
+ }
159
+ } as const satisfies Record<string, z.infer<typeof RecordColumnsConfigSchema>>
160
+
161
+ export type ActionRegistry = TopLevelAction[]
162
+
163
+ /**
164
+ * Generic empty default for the action registry.
165
+ * The Elevasis-specific lead-gen action list has been relocated to
166
+ * `@repo/elevasis-core/src/organization-model/actions.ts`.
167
+ */
168
+ export const ACTION_REGISTRY: ActionRegistry = []
169
+
170
+ export function findActionById(id: string): TopLevelAction | undefined {
171
+ return findOrganizationActionById(id)
172
+ }
173
+
174
+ export const PROSPECTING_STEPS = {
175
+ localServices: {
176
+ sourceCompanies: {
177
+ id: 'source-companies',
178
+ label: 'Companies found',
179
+ primaryEntity: 'company',
180
+ outputs: ['company'],
181
+ stageKey: 'populated',
182
+ dependencyMode: 'per-record-eligibility',
183
+ actionKey: 'lead-gen.company.source',
184
+ defaultBatchSize: 100,
185
+ maxBatchSize: 250
186
+ },
187
+ analyzeWebsites: {
188
+ id: 'analyze-websites',
189
+ label: 'Websites analyzed',
190
+ primaryEntity: 'company',
191
+ outputs: ['company'],
192
+ stageKey: 'extracted',
193
+ dependsOn: ['source-companies'],
194
+ dependencyMode: 'per-record-eligibility',
195
+ actionKey: 'lead-gen.company.website-extract',
196
+ defaultBatchSize: 50,
197
+ maxBatchSize: 100
198
+ },
199
+ qualifyCompanies: {
200
+ id: 'qualify-companies',
201
+ label: 'Companies qualified',
202
+ primaryEntity: 'company',
203
+ outputs: ['company'],
204
+ stageKey: 'qualified',
205
+ dependsOn: ['analyze-websites'],
206
+ dependencyMode: 'per-record-eligibility',
207
+ actionKey: 'lead-gen.company.qualify',
208
+ defaultBatchSize: 100,
209
+ maxBatchSize: 250
210
+ },
211
+ findContacts: {
212
+ id: 'find-contacts',
213
+ label: 'Decision-makers found',
214
+ primaryEntity: 'contact',
215
+ outputs: ['contact'],
216
+ stageKey: 'discovered',
217
+ dependsOn: ['qualify-companies'],
218
+ dependencyMode: 'per-record-eligibility',
219
+ actionKey: 'lead-gen.contact.discover',
220
+ defaultBatchSize: 50,
221
+ maxBatchSize: 100
222
+ },
223
+ verifyEmails: {
224
+ id: 'verify-emails',
225
+ label: 'Emails verified',
226
+ primaryEntity: 'contact',
227
+ outputs: ['contact'],
228
+ stageKey: 'verified',
229
+ dependsOn: ['find-contacts'],
230
+ dependencyMode: 'per-record-eligibility',
231
+ actionKey: 'lead-gen.contact.verify-email',
232
+ defaultBatchSize: 100,
233
+ maxBatchSize: 500
234
+ },
235
+ personalize: {
236
+ id: 'personalize',
237
+ label: 'Personalize',
238
+ primaryEntity: 'contact',
239
+ outputs: ['contact'],
240
+ stageKey: 'personalized',
241
+ dependsOn: ['verify-emails'],
242
+ dependencyMode: 'per-record-eligibility',
243
+ actionKey: 'lead-gen.contact.personalize',
244
+ defaultBatchSize: 25,
245
+ maxBatchSize: 100
246
+ },
247
+ review: {
248
+ id: 'review',
249
+ label: 'Reviewed and exported',
250
+ primaryEntity: 'contact',
251
+ outputs: ['export'],
252
+ stageKey: 'uploaded',
253
+ dependsOn: ['personalize'],
254
+ dependencyMode: 'per-record-eligibility',
255
+ actionKey: 'lead-gen.review.outreach-ready',
256
+ defaultBatchSize: 25,
257
+ maxBatchSize: 100
258
+ }
259
+ },
260
+ dtcApolloClickup: {
261
+ importApolloSearch: {
262
+ id: 'import-apollo-search',
263
+ label: 'Companies found',
264
+ description: 'Pull companies and seed contact data from a predefined Apollo search or list.',
265
+ primaryEntity: 'company',
266
+ outputs: ['company', 'contact'],
267
+ stageKey: 'populated',
268
+ dependencyMode: 'per-record-eligibility',
269
+ actionKey: 'lead-gen.company.apollo-import',
270
+ defaultBatchSize: 250,
271
+ maxBatchSize: 1000,
272
+ recordColumns: DTC_RECORD_COLUMNS.populated,
273
+ credentialRequirements: [
274
+ {
275
+ key: 'apollo',
276
+ provider: 'apollo',
277
+ credentialType: 'api-key-secret',
278
+ label: 'Apollo API key',
279
+ required: true,
280
+ selectionMode: 'single',
281
+ inputPath: 'credential'
282
+ }
283
+ ]
284
+ },
285
+ apifyCrawl: {
286
+ id: 'apify-crawl',
287
+ label: 'Websites crawled',
288
+ description:
289
+ '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.',
290
+ primaryEntity: 'company',
291
+ outputs: ['company'],
292
+ stageKey: 'crawled',
293
+ dependsOn: ['import-apollo-search'],
294
+ dependencyMode: 'per-record-eligibility',
295
+ actionKey: 'lead-gen.company.apify-crawl',
296
+ defaultBatchSize: 50,
297
+ maxBatchSize: 100,
298
+ recordColumns: DTC_RECORD_COLUMNS.crawled,
299
+ credentialRequirements: [
300
+ {
301
+ key: 'apify',
302
+ provider: 'apify',
303
+ credentialType: 'api-key-secret',
304
+ label: 'Apify API token',
305
+ required: true,
306
+ selectionMode: 'single',
307
+ inputPath: 'credential',
308
+ verifyOnRun: true
309
+ }
310
+ ]
311
+ },
312
+ analyzeWebsites: {
313
+ id: 'analyze-websites',
314
+ label: 'Websites analyzed',
315
+ description: 'Extract subscription, product, retention, and tech-stack signals from each brand website.',
316
+ primaryEntity: 'company',
317
+ outputs: ['company'],
318
+ stageKey: 'extracted',
319
+ dependsOn: ['apify-crawl'],
320
+ dependencyMode: 'per-record-eligibility',
321
+ actionKey: 'lead-gen.company.website-extract',
322
+ defaultBatchSize: 50,
323
+ maxBatchSize: 100,
324
+ recordColumns: DTC_RECORD_COLUMNS.extracted
325
+ },
326
+ scoreDtcFit: {
327
+ id: 'score-dtc-fit',
328
+ label: 'Companies qualified',
329
+ description: 'Classify subscription potential, consumable-product fit, retention maturity, and disqualifiers.',
330
+ primaryEntity: 'company',
331
+ outputs: ['company'],
332
+ stageKey: 'qualified',
333
+ dependsOn: ['analyze-websites'],
334
+ dependencyMode: 'per-record-eligibility',
335
+ actionKey: 'lead-gen.company.dtc-subscription-qualify',
336
+ defaultBatchSize: 100,
337
+ maxBatchSize: 250,
338
+ recordColumns: DTC_RECORD_COLUMNS.qualified
339
+ },
340
+ enrichDecisionMakers: {
341
+ id: 'enrich-decision-makers',
342
+ label: 'Decision-makers found',
343
+ description:
344
+ 'Use Apollo to find qualified contacts at qualified companies - founders, retention leads, lifecycle leads, and marketing owners.',
345
+ primaryEntity: 'company',
346
+ outputs: ['contact'],
347
+ stageKey: 'decision-makers-enriched',
348
+ recordEntity: 'contact',
349
+ dependsOn: ['score-dtc-fit'],
350
+ dependencyMode: 'per-record-eligibility',
351
+ actionKey: 'lead-gen.contact.apollo-decision-maker-enrich',
352
+ defaultBatchSize: 100,
353
+ maxBatchSize: 250,
354
+ recordColumns: DTC_RECORD_COLUMNS.decisionMakers,
355
+ credentialRequirements: [
356
+ {
357
+ key: 'apollo',
358
+ provider: 'apollo',
359
+ credentialType: 'api-key-secret',
360
+ label: 'Apollo API key',
361
+ required: true,
362
+ selectionMode: 'single',
363
+ inputPath: 'credential'
364
+ }
365
+ ]
366
+ },
367
+ reviewAndExport: {
368
+ id: 'review-and-export',
369
+ label: 'Reviewed and exported',
370
+ description:
371
+ 'Operator QC approves or rejects qualified companies, then approved records are exported as a lead list with unverified emails.',
372
+ primaryEntity: 'company',
373
+ outputs: ['export'],
374
+ stageKey: 'uploaded',
375
+ recordsStageKey: 'uploaded',
376
+ recordSourceStageKey: 'qualified',
377
+ dependsOn: ['enrich-decision-makers'],
378
+ dependencyMode: 'per-record-eligibility',
379
+ actionKey: 'lead-gen.export.list',
380
+ defaultBatchSize: 100,
381
+ maxBatchSize: 250,
382
+ recordColumns: DTC_RECORD_COLUMNS.uploaded,
383
+ credentialRequirements: [
384
+ {
385
+ key: 'clickup',
386
+ provider: 'clickup',
387
+ credentialType: 'api-key-secret',
388
+ label: 'ClickUp API token',
389
+ required: true,
390
+ selectionMode: 'single',
391
+ inputPath: 'clickupCredential',
392
+ verifyOnRun: true
393
+ }
394
+ ]
395
+ }
396
+ }
397
+ } as const satisfies Record<TemplateName, Record<StepName, ListBuilderStep>>
398
+
399
+ // OrganizationModelProspectingSchema and DEFAULT_ORGANIZATION_MODEL_PROSPECTING are retired.
400
+ // Build-template/stage data lives in System.ontology.catalogTypes. Use
401
+ // getAllBuildTemplates() / getAllProspectingStages() from migration-helpers to
402
+ // read prospecting data portably.
403
+ //
404
+ // ProspectingBuildTemplateSchema, ProspectingBuildTemplateStepSchema, ProspectingLifecycleStageSchema,
405
+ // PROSPECTING_STEPS, and ACTION_REGISTRY are retained for use by business logic and UI.