@elevasis/core 0.25.0 → 0.27.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 (80) hide show
  1. package/dist/index.d.ts +171 -90
  2. package/dist/index.js +377 -1541
  3. package/dist/knowledge/index.d.ts +48 -59
  4. package/dist/knowledge/index.js +1 -1
  5. package/dist/organization-model/index.d.ts +171 -90
  6. package/dist/organization-model/index.js +377 -1541
  7. package/dist/test-utils/index.d.ts +25 -33
  8. package/dist/test-utils/index.js +250 -1357
  9. package/package.json +1 -1
  10. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +990 -1065
  11. package/src/business/acquisition/api-schemas.test.ts +1996 -1882
  12. package/src/business/acquisition/api-schemas.ts +1478 -1502
  13. package/src/business/acquisition/crm-next-action.test.ts +45 -25
  14. package/src/business/acquisition/crm-next-action.ts +227 -220
  15. package/src/business/acquisition/crm-priority.test.ts +41 -8
  16. package/src/business/acquisition/crm-priority.ts +365 -349
  17. package/src/business/acquisition/crm-state-actions.test.ts +208 -153
  18. package/src/business/acquisition/derive-actions.test.ts +101 -34
  19. package/src/business/acquisition/derive-actions.ts +8 -92
  20. package/src/business/acquisition/ontology-validation.ts +76 -162
  21. package/src/business/acquisition/types.ts +7 -8
  22. package/src/business/pdf/sections/investment.ts +1 -1
  23. package/src/business/pdf/sections/summary-investment.ts +1 -1
  24. package/src/execution/engine/tools/tool-maps.ts +872 -831
  25. package/src/knowledge/queries.ts +0 -1
  26. package/src/organization-model/__tests__/content-kinds-registry.test.ts +35 -210
  27. package/src/organization-model/__tests__/cross-ref.test.ts +167 -0
  28. package/src/organization-model/__tests__/defaults.test.ts +4 -4
  29. package/src/organization-model/__tests__/domains/actions.test.ts +12 -36
  30. package/src/organization-model/__tests__/domains/offerings.test.ts +13 -6
  31. package/src/organization-model/__tests__/domains/resources.test.ts +497 -350
  32. package/src/organization-model/__tests__/domains/systems.test.ts +6 -7
  33. package/src/organization-model/__tests__/flatten-additive-merge.test.ts +68 -80
  34. package/src/organization-model/__tests__/foundation.test.ts +81 -14
  35. package/src/organization-model/__tests__/graph.test.ts +662 -694
  36. package/src/organization-model/__tests__/knowledge.test.ts +31 -17
  37. package/src/organization-model/__tests__/lookup-helpers.test.ts +128 -438
  38. package/src/organization-model/__tests__/migration-helpers.test.ts +362 -591
  39. package/src/organization-model/__tests__/prospecting-ssot.test.ts +68 -103
  40. package/src/organization-model/__tests__/published-zero-leak.test.ts +76 -0
  41. package/src/organization-model/__tests__/recursive-system-schema.test.ts +159 -532
  42. package/src/organization-model/__tests__/resolve.test.ts +79 -42
  43. package/src/organization-model/__tests__/schema-refinements.test.ts +72 -0
  44. package/src/organization-model/__tests__/schema.test.ts +65 -56
  45. package/src/organization-model/catalogs/lead-gen.ts +0 -103
  46. package/src/organization-model/cross-ref.ts +175 -0
  47. package/src/organization-model/defaults.ts +17 -702
  48. package/src/organization-model/domains/actions.ts +116 -333
  49. package/src/organization-model/domains/branding.ts +6 -6
  50. package/src/organization-model/domains/knowledge.ts +15 -7
  51. package/src/organization-model/domains/projects.ts +4 -4
  52. package/src/organization-model/domains/prospecting.ts +405 -395
  53. package/src/organization-model/domains/resources.ts +206 -135
  54. package/src/organization-model/domains/sales.test.ts +104 -218
  55. package/src/organization-model/domains/sales.ts +217 -380
  56. package/src/organization-model/domains/systems.ts +8 -23
  57. package/src/organization-model/graph/build.ts +223 -294
  58. package/src/organization-model/graph/schema.ts +2 -3
  59. package/src/organization-model/graph/types.ts +12 -14
  60. package/src/organization-model/helpers.ts +130 -218
  61. package/src/organization-model/index.ts +105 -124
  62. package/src/organization-model/migration-helpers.ts +211 -249
  63. package/src/organization-model/ontology.ts +0 -60
  64. package/src/organization-model/organization-graph.mdx +4 -5
  65. package/src/organization-model/organization-model.mdx +1 -1
  66. package/src/organization-model/published.ts +236 -226
  67. package/src/organization-model/resolve.ts +4 -5
  68. package/src/organization-model/schema-refinements.ts +667 -0
  69. package/src/organization-model/schema.ts +88 -889
  70. package/src/organization-model/types.ts +167 -161
  71. package/src/platform/registry/__tests__/validation.test.ts +23 -0
  72. package/src/platform/registry/validation.ts +13 -2
  73. package/src/reference/_generated/contracts.md +990 -1065
  74. package/src/organization-model/content-kinds/config.ts +0 -36
  75. package/src/organization-model/content-kinds/index.ts +0 -78
  76. package/src/organization-model/content-kinds/pipeline.ts +0 -68
  77. package/src/organization-model/content-kinds/registry.ts +0 -44
  78. package/src/organization-model/content-kinds/status.ts +0 -71
  79. package/src/organization-model/content-kinds/template.ts +0 -83
  80. 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.