@elevasis/core 0.13.0 → 0.14.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 (41) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +9 -2
  3. package/dist/organization-model/index.d.ts +1 -1
  4. package/dist/organization-model/index.js +9 -2
  5. package/dist/test-utils/index.d.ts +463 -377
  6. package/dist/test-utils/index.js +9 -2
  7. package/package.json +1 -1
  8. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +2324 -0
  9. package/src/business/acquisition/activity-events.test.ts +250 -0
  10. package/src/business/acquisition/activity-events.ts +7 -65
  11. package/src/business/acquisition/api-schemas.test.ts +1180 -0
  12. package/src/business/acquisition/api-schemas.ts +1075 -859
  13. package/src/business/acquisition/crm-state-actions.test.ts +160 -0
  14. package/src/business/acquisition/derive-actions.test.ts +518 -0
  15. package/src/business/acquisition/derive-actions.ts +103 -90
  16. package/src/business/acquisition/index.ts +149 -111
  17. package/src/business/acquisition/stateful.ts +30 -0
  18. package/src/business/acquisition/types.ts +44 -77
  19. package/src/execution/engine/index.ts +437 -434
  20. package/src/execution/engine/tools/integration/server/adapters/attio/__tests__/attio-crud.integration.test.ts +363 -360
  21. package/src/execution/engine/tools/integration/server/adapters/attio/fetch/get-record/index.test.ts +162 -186
  22. package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-records/index.test.ts +316 -338
  23. package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-adapter.ts +204 -210
  24. package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.test.ts +88 -0
  25. package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.ts +141 -134
  26. package/src/execution/engine/tools/integration/server/adapters/resend/fetch/utils/types.ts +76 -75
  27. package/src/execution/engine/tools/integration/service.test.ts +34 -9
  28. package/src/execution/engine/tools/integration/service.ts +6 -3
  29. package/src/execution/engine/tools/lead-service-types.ts +945 -888
  30. package/src/execution/engine/tools/platform/acquisition/types.ts +266 -260
  31. package/src/execution/engine/tools/registry.ts +701 -699
  32. package/src/execution/engine/tools/tool-maps.ts +816 -791
  33. package/src/execution/engine/workflow/types.ts +11 -0
  34. package/src/organization-model/contracts.ts +4 -4
  35. package/src/organization-model/domains/navigation.ts +62 -62
  36. package/src/organization-model/domains/sales.ts +272 -0
  37. package/src/organization-model/published.ts +21 -21
  38. package/src/organization-model/resolve.ts +21 -8
  39. package/src/platform/constants/versions.ts +1 -1
  40. package/src/reference/_generated/contracts.md +2324 -0
  41. package/src/supabase/database.types.ts +2958 -2886
@@ -1,888 +1,945 @@
1
- /**
2
- * Lead Service Types
3
- * CRUD operation types for the acquisition platform (lists, companies, contacts, deals)
4
- *
5
- * Implementation: apps/api/src/acquisition/lead-service.ts (LeadService class)
6
- */
7
-
8
- import type { Json } from '../../../supabase'
9
-
10
- // Re-export acquisition domain types from the authoritative source
11
- import type {
12
- AcqList,
13
- AcqCompany,
14
- AcqContact,
15
- AcqDealTask,
16
- AcqDealTaskKind,
17
- CompanyListStage,
18
- ContactListStage,
19
- ListConfig,
20
- ListTelemetry
21
- } from '../../../business/acquisition/types'
22
-
23
- export type {
24
- AcqList,
25
- AcqCompany,
26
- AcqContact,
27
- AcqDealTask,
28
- AcqDealTaskKind,
29
- CompanyListStage,
30
- ContactListStage,
31
- ListConfig,
32
- ListTelemetry
33
- }
34
-
35
- // Pagination types
36
- export interface PaginationParams {
37
- limit: number
38
- offset: number
39
- }
40
-
41
- export interface PaginatedResult<T> {
42
- data: T[]
43
- total: number
44
- limit: number
45
- offset: number
46
- }
47
-
48
- // List params
49
- export interface CreateListParams {
50
- organizationId: string
51
- name: string
52
- description?: string
53
- type?: string
54
- batchIds?: string[]
55
- instantlyCampaignId?: string
56
- status?: string
57
- metadata?: Record<string, unknown>
58
- config?: ListConfig
59
- }
60
-
61
- export interface UpdateListParams {
62
- name?: string
63
- description?: string
64
- status?: string
65
- batchIds?: string[]
66
- }
67
-
68
- // Company params
69
- export interface CreateCompanyParams {
70
- organizationId: string
71
- name: string
72
- domain?: string
73
- linkedinUrl?: string
74
- website?: string
75
- numEmployees?: number
76
- foundedYear?: number
77
- locationCity?: string
78
- locationState?: string
79
- category?: string
80
- source?: string
81
- batchId?: string
82
- verticalResearch?: string
83
- }
84
-
85
- export interface UpdateCompanyParams {
86
- name?: string
87
- domain?: string
88
- linkedinUrl?: string
89
- website?: string
90
- numEmployees?: number
91
- foundedYear?: number
92
- locationCity?: string
93
- locationState?: string
94
- category?: string
95
- segment?: string
96
- pipelineStatus?: Record<string, unknown>
97
- enrichmentData?: Record<string, unknown>
98
- source?: string
99
- batchId?: string
100
- status?: 'active' | 'invalid'
101
- verticalResearch?: string | null
102
- }
103
-
104
- export type UpsertCompanyParams = CreateCompanyParams
105
- // Upsert by domain - uses same fields as create
106
-
107
- export interface CompanyFilters {
108
- listId?: string // Filter to companies in a specific list (via acq_list_companies)
109
- search?: string
110
- domain?: string
111
- website?: string
112
- segment?: string
113
- category?: string
114
- pipelineStatus?: Record<string, unknown>
115
- /** Exclude companies whose pipeline_status contains this value (PostgREST NOT contains) */
116
- pipelineStatusNot?: Record<string, unknown>
117
- batchId?: string
118
- status?: 'active' | 'invalid'
119
- includeAll?: boolean
120
- excludeColumns?: Array<'enrichmentData' | 'pipelineStatus'>
121
- }
122
-
123
- // Contact params
124
- export interface CreateContactParams {
125
- organizationId: string
126
- email: string
127
- companyId?: string
128
- firstName?: string
129
- lastName?: string
130
- linkedinUrl?: string
131
- title?: string
132
- source?: string
133
- sourceId?: string
134
- batchId?: string
135
- }
136
-
137
- export interface UpdateContactParams {
138
- companyId?: string
139
- emailValid?: 'VALID' | 'INVALID' | 'RISKY' | 'UNKNOWN'
140
- firstName?: string
141
- lastName?: string
142
- linkedinUrl?: string
143
- title?: string
144
- headline?: string
145
- filterReason?: string
146
- openingLine?: string
147
- pipelineStatus?: Record<string, unknown>
148
- enrichmentData?: Record<string, unknown>
149
- status?: 'active' | 'invalid'
150
- }
151
-
152
- export type UpsertContactParams = CreateContactParams
153
- // Upsert by email - uses same fields as create
154
-
155
- export interface ContactFilters {
156
- listId?: string // Filter to contacts in a specific list (via acq_list_members)
157
- search?: string
158
- openingLineIsNull?: boolean // Filter to contacts without personalization
159
- pipelineStatus?: Record<string, unknown>
160
- batchId?: string
161
- contactStatus?: 'active' | 'invalid' // Filter by contact status (soft-delete flag)
162
- }
163
-
164
- // Deal params (for acq_deals table)
165
- export interface UpsertDealParams {
166
- organizationId: string
167
- /** Contact email — dedupe key together with organization_id */
168
- contactEmail: string
169
- /** Optional contact ID for foreign key join */
170
- contactId?: string
171
- /** Campaign list that generated this deal (FK to acq_lists) */
172
- sourceListId?: string
173
- /** Deal origin: 'instantly', 'referral', 'inbound', 'manual' */
174
- sourceType?: 'instantly' | 'referral' | 'inbound' | 'manual'
175
- /** Optional discovery data JSONB to set on upsert */
176
- discoveryData?: unknown
177
- /** Optional proposal data JSONB to set on upsert */
178
- proposalData?: unknown
179
- }
180
-
181
- export interface UpdateDiscoveryDataParams {
182
- organizationId: string
183
- contactEmail: string
184
- discoveryData: unknown
185
- submittedBy?: string
186
- }
187
-
188
- export interface UpdateProposalDataParams {
189
- organizationId: string
190
- contactEmail: string
191
- proposalData: unknown
192
- proposalPdfUrl?: string
193
- }
194
-
195
- export interface MarkProposalSentParams {
196
- organizationId: string
197
- contactEmail: string
198
- }
199
-
200
- export interface MarkProposalReviewedParams {
201
- organizationId: string
202
- contactEmail: string
203
- reviewedBy: string
204
- proposalData?: unknown
205
- }
206
-
207
- export interface UpdateCloseLostReasonParams {
208
- organizationId: string
209
- dealId: string
210
- reason: string
211
- }
212
-
213
- export interface UpdateFeesParams {
214
- organizationId: string
215
- contactEmail?: string
216
- dealId?: string
217
- initialFee?: number
218
- monthlyFee?: number
219
- }
220
-
221
- export interface TransitionItemParams {
222
- organizationId: string
223
- dealId: string
224
- pipelineKey: string
225
- stageKey: string
226
- stateKey?: string | null
227
- reason?: string
228
- expectedUpdatedAt?: string
229
- }
230
-
231
- export interface SetContactNurtureParams {
232
- organizationId: string
233
- contactEmail: string
234
- nurture?: boolean
235
- }
236
-
237
- export interface DeactivateContactsByCompanyParams {
238
- organizationId: string
239
- companyId: string
240
- }
241
-
242
- export interface DeactivateContactsByCompanyResult {
243
- deactivated: number
244
- }
245
-
246
- export interface CancelSchedulesAndHitlByEmailParams {
247
- organizationId: string
248
- email: string
249
- }
250
-
251
- export interface CancelHitlByDealIdParams {
252
- organizationId: string
253
- dealId: string
254
- }
255
-
256
- export interface ClearDealFieldsParams {
257
- organizationId: string
258
- contactEmail?: string
259
- dealId?: string
260
- fields: (
261
- | 'proposalPdfUrl'
262
- | 'proposalGeneratedAt'
263
- | 'initialFee'
264
- | 'monthlyFee'
265
- | 'closedLostReason'
266
- | 'closedLostAt'
267
- | 'discoveryData'
268
- | 'discoverySubmittedAt'
269
- )[]
270
- }
271
-
272
- export interface DeleteDealParams {
273
- organizationId: string
274
- dealId: string
275
- }
276
-
277
- export interface GetDealByIdParams {
278
- dealId: string
279
- organizationId: string
280
- }
281
-
282
- export interface GetContactByIdParams {
283
- contactId: string
284
- organizationId: string
285
- }
286
-
287
- export interface GetCompanyByIdParams {
288
- companyId: string
289
- organizationId: string
290
- }
291
-
292
- // Social monitoring params (acq_social_posts table)
293
- export interface UpsertSocialPostParams {
294
- organizationId: string
295
- platform: string
296
- platformPostId: string
297
- authorName: string
298
- authorUrl?: string | null
299
- postTitle: string
300
- postText: string
301
- postUrl: string
302
- engagementCount?: number
303
- commentsCount?: number
304
- postedAt: string
305
- metadata?: Record<string, unknown>
306
- relevanceScore?: number
307
- matchedKeywords?: string[]
308
- matchedQuery?: string | null
309
- initialDraft?: string | null
310
- finalResponse?: string | null
311
- sourceCategory?: string | null
312
- }
313
-
314
- export interface UpsertSocialPostsParams {
315
- organizationId: string
316
- posts: Omit<UpsertSocialPostParams, 'organizationId'>[]
317
- }
318
-
319
- export interface UpsertSocialPostsResult {
320
- inserted: number
321
- duplicatesSkipped: number
322
- }
323
-
324
- export interface AcqDeal {
325
- id: string
326
- organizationId: string
327
- contactEmail: string
328
- pipelineKey: string
329
- stageKey?: string | null
330
- stateKey?: string | null
331
- discoveryData?: Json | null
332
- proposalData?: Json | null
333
- proposalSentAt?: string | null
334
- proposalPdfUrl?: string | null
335
- signatureEnvelopeId?: string | null
336
- sourceListId?: string | null
337
- sourceType?: string | null
338
- activityLog: DealActivityEntry[]
339
- createdAt: Date
340
- updatedAt: Date
341
- }
342
-
343
- export interface DealActivityEntry {
344
- type: string
345
- title: string
346
- description?: string
347
- payload?: Record<string, unknown>
348
- occurredAt: string
349
- }
350
-
351
- export interface AcqDealNote {
352
- id: string
353
- dealId: string
354
- organizationId: string
355
- authorUserId: string | null
356
- body: string
357
- createdAt: string
358
- updatedAt: string
359
- }
360
-
361
- export interface CreateDealNoteParams {
362
- organizationId: string
363
- dealId: string
364
- body: string
365
- authorUserId?: string
366
- }
367
-
368
- export interface ListDealNotesParams {
369
- organizationId: string
370
- dealId: string
371
- }
372
-
373
- export interface CreateDealTaskParams {
374
- organizationId: string
375
- dealId: string
376
- title: string
377
- description?: string | null
378
- kind?: AcqDealTaskKind
379
- dueAt?: string | null
380
- assigneeUserId?: string | null
381
- createdByUserId?: string | null
382
- }
383
-
384
- export interface ListDealTasksParams {
385
- organizationId: string
386
- dealId: string
387
- }
388
-
389
- export interface ListDealTasksDueParams {
390
- organizationId: string
391
- assigneeUserId?: string | null
392
- /** Window filter: 'overdue' = past due, 'today' = due today only, 'today_and_overdue' (default) = both, 'upcoming' = future */
393
- window?: 'overdue' | 'today' | 'today_and_overdue' | 'upcoming'
394
- }
395
-
396
- export interface CompleteDealTaskParams {
397
- organizationId: string
398
- taskId: string
399
- completedByUserId: string | null
400
- }
401
-
402
- export interface RecordDealActivityParams {
403
- organizationId: string
404
- dealId: string
405
- type: string
406
- title: string
407
- description?: string
408
- payload?: Record<string, unknown>
409
- }
410
-
411
- // Deal analytics types (for /meta status and platform-status workflow)
412
-
413
- export interface DealStageSummary {
414
- stage: string
415
- count: number
416
- oldestUpdatedAt: string | null
417
- newestUpdatedAt: string | null
418
- }
419
-
420
- export interface StaleDeal {
421
- id: string
422
- contactEmail: string
423
- stageKey: string
424
- updatedAt: string
425
- daysStale: number
426
- }
427
-
428
- export interface DealPipelineAnalytics {
429
- totalDeals: number
430
- stageSummary: DealStageSummary[]
431
- staleDeals: StaleDeal[]
432
- recentActivity: AcqDeal[]
433
- }
434
-
435
- export interface DealAnalyticsParams {
436
- organizationId: string
437
- recentLimit?: number
438
- }
439
-
440
- export interface DealFilters {
441
- stage?: string
442
- search?: string
443
- limit?: number
444
- offset?: number
445
- }
446
-
447
- export interface AddContactsToListParams {
448
- organizationId: string
449
- listId: string
450
- contactIds: string[]
451
- }
452
-
453
- export interface AddContactsToListResult {
454
- added: number
455
- alreadyExisted: number
456
- }
457
-
458
- // List config/progress/executions params
459
- export interface UpdateListConfigParams {
460
- organizationId: string
461
- listId: string
462
- /** Deep-partial patch — any subtree that is present is replaced at that level. */
463
- configPatch: Record<string, unknown>
464
- }
465
-
466
- export interface UpdateCompanyStageParams {
467
- organizationId: string
468
- listId: string
469
- companyId: string
470
- stage: CompanyListStage
471
- executionId?: string
472
- }
473
-
474
- export interface UpdateContactStageParams {
475
- organizationId: string
476
- listId: string
477
- contactId: string
478
- stage: ContactListStage
479
- executionId?: string
480
- }
481
-
482
- export interface AddCompaniesToListParams {
483
- organizationId: string
484
- listId: string
485
- companyIds: string[]
486
- }
487
-
488
- export interface AddCompaniesToListResult {
489
- added: number
490
- alreadyExisted: number
491
- }
492
-
493
- export interface RemoveCompaniesFromListParams {
494
- organizationId: string
495
- listId: string
496
- companyIds: string[]
497
- }
498
-
499
- export interface RemoveCompaniesFromListResult {
500
- removed: number
501
- }
502
-
503
- export interface RecordListExecutionParams {
504
- organizationId: string
505
- listId: string
506
- executionId: string
507
- configSnapshot?: Record<string, unknown>
508
- }
509
-
510
- export interface ListExecutionSummary {
511
- executionId: string
512
- resourceId: string
513
- status: string
514
- createdAt: string
515
- completedAt: string | null
516
- durationMs: number | null
517
- }
518
-
519
- // Bulk import (contacts)
520
- export interface BulkImportParams {
521
- organizationId: string
522
- contacts: CreateContactParams[]
523
- listId?: string
524
- }
525
-
526
- export interface BulkImportResult {
527
- created: number
528
- updated: number
529
- errors: Array<{ email: string; error: string }>
530
- }
531
-
532
- // Bulk import (companies)
533
- export interface BulkImportCompanyEntry {
534
- name: string
535
- domain: string
536
- website?: string
537
- locationCity?: string
538
- locationState?: string
539
- category?: string
540
- source?: string
541
- enrichmentData?: Record<string, unknown>
542
- pipelineStatus?: Record<string, unknown>
543
- }
544
-
545
- export interface BulkImportCompaniesParams {
546
- organizationId: string
547
- batchId: string
548
- companies: BulkImportCompanyEntry[]
549
- }
550
-
551
- export interface BulkImportCompaniesResult {
552
- created: number
553
- skipped: number
554
- errors: Array<{ companyName: string; error: string }>
555
- }
556
-
557
- /**
558
- * Lead Service interface for acquisition platform tools.
559
- * Provides CRUD operations for lists, companies, and contacts.
560
- *
561
- * Implementation: apps/api/src/acquisition/lead-service.ts (LeadService class)
562
- *
563
- * Multi-tenancy: All operations require organizationId for tenant isolation.
564
- * All queries are filtered by organizationId via RLS policies.
565
- */
566
- export interface ILeadService {
567
- // List operations
568
- /**
569
- * Create a new list
570
- * @see LeadService.createList (apps/api/src/acquisition/lead-service.ts)
571
- */
572
- createList(params: CreateListParams): Promise<AcqList>
573
-
574
- /**
575
- * Update an existing list
576
- * @see LeadService.updateList (apps/api/src/acquisition/lead-service.ts)
577
- */
578
- updateList(id: string, params: UpdateListParams): Promise<AcqList>
579
-
580
- /**
581
- * Delete a list
582
- * @see LeadService.deleteList (apps/api/src/acquisition/lead-service.ts)
583
- */
584
- deleteList(id: string, organizationId: string): Promise<void>
585
-
586
- /**
587
- * Add contacts to a list (upsert — idempotent on re-runs)
588
- * @see LeadService.addContactsToList (apps/api/src/business/acquisition/lead-service.ts)
589
- */
590
- addContactsToList(params: AddContactsToListParams): Promise<AddContactsToListResult>
591
-
592
- /**
593
- * List all lists for an organization
594
- * @see LeadService.listLists (apps/api/src/acquisition/lead-service.ts)
595
- */
596
- listLists(organizationId: string): Promise<AcqList[]>
597
-
598
- /**
599
- * Get a single list by ID.
600
- */
601
- getList(id: string, organizationId: string): Promise<AcqList | null>
602
-
603
- /**
604
- * Deep-merge patch the jsonb `config` column. Patch keys at any depth
605
- * replace the corresponding subtree.
606
- */
607
- updateListConfig(params: UpdateListConfigParams): Promise<AcqList>
608
-
609
- /**
610
- * Add companies to a list via the acq_list_companies junction.
611
- * Idempotent on (list_id, company_id).
612
- */
613
- addCompaniesToList(params: AddCompaniesToListParams): Promise<AddCompaniesToListResult>
614
-
615
- /**
616
- * Remove companies from a list (delete junction rows only — company rows untouched).
617
- */
618
- removeCompaniesFromList(params: RemoveCompaniesFromListParams): Promise<RemoveCompaniesFromListResult>
619
-
620
- /**
621
- * Live org-wide list telemetry — computed on demand from acq_companies
622
- * and acq_contacts joined through the acq_list_companies / acq_list_members
623
- * junctions. Replaces the batch-scoped getBatchTelemetry.
624
- */
625
- getListsTelemetry(organizationId: string): Promise<ListTelemetry[]>
626
-
627
- /**
628
- * Single live rollup read from list junction stage columns.
629
- */
630
- getListProgress(listId: string, organizationId: string): Promise<ListTelemetry | null>
631
-
632
- /**
633
- * Advance a company row within a list's explicit stage journey.
634
- */
635
- updateCompanyStage(params: UpdateCompanyStageParams): Promise<void>
636
-
637
- /**
638
- * Advance a contact row within a list's explicit stage journey.
639
- */
640
- updateContactStage(params: UpdateContactStageParams): Promise<void>
641
-
642
- /**
643
- * Per-list execution history — reads via the feature-owned
644
- * acq_list_executions junction, joined to execution_logs for details.
645
- */
646
- getListExecutions(listId: string, organizationId: string): Promise<ListExecutionSummary[]>
647
-
648
- /**
649
- * Write a junction row linking (listId, executionId). Called by the
650
- * workflow layer at execution start in Step 4. No-op if the row exists.
651
- */
652
- recordListExecution(params: RecordListExecutionParams): Promise<void>
653
-
654
- // Company operations
655
- /**
656
- * Create a new company
657
- * @see LeadService.createCompany (apps/api/src/acquisition/lead-service.ts)
658
- */
659
- createCompany(params: CreateCompanyParams): Promise<AcqCompany>
660
-
661
- /**
662
- * Update an existing company
663
- * @see LeadService.updateCompany (apps/api/src/acquisition/lead-service.ts)
664
- */
665
- updateCompany(id: string, params: UpdateCompanyParams): Promise<AcqCompany>
666
-
667
- /**
668
- * Upsert a company by domain
669
- * @see LeadService.upsertCompany (apps/api/src/acquisition/lead-service.ts)
670
- */
671
- upsertCompany(params: UpsertCompanyParams): Promise<AcqCompany>
672
-
673
- /**
674
- * Get a company by ID
675
- * @see LeadService.getCompany (apps/api/src/acquisition/lead-service.ts)
676
- */
677
- getCompany(id: string, organizationId: string): Promise<AcqCompany | null>
678
-
679
- /**
680
- * List companies with optional filters
681
- * @see LeadService.listCompanies (apps/api/src/acquisition/lead-service.ts)
682
- */
683
- listCompanies(organizationId: string, filters: CompanyFilters): Promise<AcqCompany[]>
684
-
685
- /**
686
- * Delete a company
687
- * @see LeadService.deleteCompany (apps/api/src/acquisition/lead-service.ts)
688
- */
689
- deleteCompany(id: string, organizationId: string): Promise<void>
690
-
691
- // Contact operations
692
- /**
693
- * Create a new contact
694
- * @see LeadService.createContact (apps/api/src/acquisition/lead-service.ts)
695
- */
696
- createContact(params: CreateContactParams): Promise<AcqContact>
697
-
698
- /**
699
- * Update an existing contact
700
- * @see LeadService.updateContact (apps/api/src/acquisition/lead-service.ts)
701
- */
702
- updateContact(id: string, params: UpdateContactParams): Promise<AcqContact>
703
-
704
- /**
705
- * Upsert a contact by email
706
- * @see LeadService.upsertContact (apps/api/src/acquisition/lead-service.ts)
707
- */
708
- upsertContact(params: UpsertContactParams): Promise<AcqContact>
709
-
710
- /**
711
- * Get a contact by ID
712
- * @see LeadService.getContact (apps/api/src/acquisition/lead-service.ts)
713
- */
714
- getContact(id: string, organizationId: string): Promise<AcqContact | null>
715
-
716
- /**
717
- * List contacts with pagination and filters
718
- * @see LeadService.listContacts (apps/api/src/acquisition/lead-service.ts)
719
- */
720
- listContacts(
721
- organizationId: string,
722
- filters: ContactFilters,
723
- pagination: PaginationParams
724
- ): Promise<PaginatedResult<AcqContact>>
725
-
726
- /**
727
- * Delete a contact
728
- * @see LeadService.deleteContact (apps/api/src/acquisition/lead-service.ts)
729
- */
730
- deleteContact(id: string, organizationId: string): Promise<void>
731
-
732
- /**
733
- * Bulk import contacts
734
- * @see LeadService.bulkImportContacts (apps/api/src/acquisition/lead-service.ts)
735
- */
736
- bulkImportContacts(params: BulkImportParams): Promise<BulkImportResult>
737
-
738
- /**
739
- * Bulk import companies with domain dedup (skips existing domains).
740
- * Inserts in batches using Supabase bulk insert + ON CONFLICT.
741
- * @see LeadService.bulkImportCompanies (apps/api/src/business/acquisition/lead-service.ts)
742
- */
743
- bulkImportCompanies(params: BulkImportCompaniesParams): Promise<BulkImportCompaniesResult>
744
-
745
- /**
746
- * Deactivate all active contacts belonging to a company.
747
- * Used by qualification workflow to cascade company disqualification to contacts.
748
- */
749
- deactivateContactsByCompany(params: DeactivateContactsByCompanyParams): Promise<DeactivateContactsByCompanyResult>
750
-
751
- /**
752
- * Get a contact by email address
753
- * Used for looking up existing leads when they reply to outreach
754
- * @see LeadService.getContactByEmail (apps/api/src/acquisition/lead-service.ts)
755
- */
756
- getContactByEmail(email: string, organizationId: string): Promise<AcqContact | null>
757
-
758
- // Deal operations (acq_deals table)
759
- /**
760
- * Upsert a deal by Attio Deal ID
761
- * Creates or updates acq_deals record linking Attio Deal to Supabase
762
- * @see LeadService.upsertDeal (apps/api/src/acquisition/lead-service.ts)
763
- */
764
- upsertDeal(params: UpsertDealParams): Promise<AcqDeal>
765
-
766
- /**
767
- * Get a deal by contact email
768
- * Used for looking up existing deals when leads book via email (fallback lookup)
769
- * @see LeadService.getDealByEmail (apps/api/src/acquisition/lead-service.ts)
770
- */
771
- getDealByEmail(email: string, organizationId: string): Promise<AcqDeal | null>
772
-
773
- /**
774
- * Get a deal by SignatureAPI envelope ID
775
- * Used by webhook handler to find deal when contract is signed
776
- * @see LeadService.getDealByEnvelopeId (apps/api/src/acquisition/lead-service.ts)
777
- */
778
- getDealByEnvelopeId(envelopeId: string, organizationId: string): Promise<AcqDeal | null>
779
-
780
- /**
781
- * Update deal with signature envelope ID
782
- * Called when proposal is sent via SignatureAPI
783
- * @see LeadService.updateDealEnvelopeId (apps/api/src/acquisition/lead-service.ts)
784
- */
785
- updateDealEnvelopeId(dealId: string, envelopeId: string, organizationId: string): Promise<AcqDeal | null>
786
-
787
- // Deal-sync operations (mirror deal-sync.ts utilities as server-side methods)
788
-
789
- getDealById(params: GetDealByIdParams): Promise<AcqDeal | null>
790
-
791
- getContactById(params: GetContactByIdParams): Promise<AcqContact | null>
792
-
793
- getCompanyById(params: GetCompanyByIdParams): Promise<AcqCompany | null>
794
-
795
- updateDiscoveryData(params: UpdateDiscoveryDataParams): Promise<void>
796
-
797
- updateProposalData(params: UpdateProposalDataParams): Promise<void>
798
-
799
- markProposalSent(params: MarkProposalSentParams): Promise<void>
800
-
801
- markProposalReviewed(params: MarkProposalReviewedParams): Promise<void>
802
-
803
- updateCloseLostReason(params: UpdateCloseLostReasonParams): Promise<void>
804
-
805
- updateFees(params: UpdateFeesParams): Promise<void>
806
-
807
- transitionItem(params: TransitionItemParams): Promise<void>
808
-
809
- setContactNurture(params: SetContactNurtureParams): Promise<void>
810
-
811
- cancelSchedulesAndHitlByEmail(
812
- params: CancelSchedulesAndHitlByEmailParams
813
- ): Promise<{ schedulesCancelled: number; hitlDeleted: number }>
814
-
815
- cancelHitlByDealId(params: CancelHitlByDealIdParams): Promise<{ hitlDeleted: number }>
816
-
817
- clearDealFields(params: ClearDealFieldsParams): Promise<void>
818
-
819
- deleteDeal(params: DeleteDealParams): Promise<void>
820
-
821
- listDeals(organizationId: string, filters?: DealFilters): Promise<AcqDeal[]>
822
-
823
- getDealPipelineAnalytics(organizationId: string, recentLimit?: number): Promise<DealPipelineAnalytics>
824
-
825
- /**
826
- * Deep-merge enrichment data into a company or contact record.
827
- * Merges per source key rather than wholesale replacing the JSONB object.
828
- * @see LeadService.mergeEnrichmentData (apps/api/src/business/acquisition/lead-service.ts)
829
- */
830
- mergeEnrichmentData(
831
- id: string,
832
- orgId: string,
833
- table: 'acq_companies' | 'acq_contacts',
834
- data: Record<string, unknown>
835
- ): Promise<void>
836
-
837
- // Deal note operations (acq_deal_notes table)
838
- /**
839
- * Create a human-authored note on a deal
840
- * @see LeadService.createDealNote (apps/api/src/business/acquisition/lead-service.ts)
841
- */
842
- createDealNote(params: CreateDealNoteParams): Promise<AcqDealNote>
843
-
844
- /**
845
- * List notes for a deal, ordered by created_at DESC
846
- * @see LeadService.listDealNotes (apps/api/src/business/acquisition/lead-service.ts)
847
- */
848
- listDealNotes(params: ListDealNotesParams): Promise<AcqDealNote[]>
849
-
850
- // Deal task operations (acq_deal_tasks table)
851
- /**
852
- * Creates a new task attached to a deal.
853
- * @see LeadService.createDealTask (apps/api/src/business/acquisition/lead-service.ts)
854
- */
855
- createDealTask(params: CreateDealTaskParams): Promise<AcqDealTask>
856
-
857
- /**
858
- * Lists all tasks for a given deal, ordered by due date.
859
- * @see LeadService.listDealTasks (apps/api/src/business/acquisition/lead-service.ts)
860
- */
861
- listDealTasks(params: ListDealTasksParams): Promise<AcqDealTask[]>
862
-
863
- /**
864
- * Lists open (uncompleted) tasks within a date window across all deals.
865
- * @see LeadService.listDealTasksDue (apps/api/src/business/acquisition/lead-service.ts)
866
- */
867
- listDealTasksDue(params: ListDealTasksDueParams): Promise<AcqDealTask[]>
868
-
869
- /**
870
- * Marks a task as completed.
871
- * @see LeadService.completeDealTask (apps/api/src/business/acquisition/lead-service.ts)
872
- */
873
- completeDealTask(params: CompleteDealTaskParams): Promise<AcqDealTask>
874
-
875
- /**
876
- * Record a deal activity entry by deal ID.
877
- * Generic method for any activity type — used by SDK workflows.
878
- * @see LeadService.recordDealActivity (apps/api/src/business/acquisition/lead-service.ts)
879
- */
880
- recordDealActivity(params: RecordDealActivityParams): Promise<void>
881
-
882
- // Social monitoring operations (acq_social_posts table)
883
- /**
884
- * Bulk upsert social posts (deduplicate by platform + platform_post_id)
885
- * @see LeadService.upsertSocialPosts (apps/api/src/business/acquisition/lead-service.ts)
886
- */
887
- upsertSocialPosts(params: UpsertSocialPostsParams): Promise<UpsertSocialPostsResult>
888
- }
1
+ /**
2
+ * Lead Service Types
3
+ * CRUD operation types for the acquisition platform (lists, companies, contacts, deals)
4
+ *
5
+ * Implementation: apps/api/src/acquisition/lead-service.ts (LeadService class)
6
+ */
7
+
8
+ import type { Json } from '../../../supabase'
9
+
10
+ // Re-export acquisition domain types from the authoritative source
11
+ import type {
12
+ AcqList,
13
+ AcqCompany,
14
+ AcqContact,
15
+ AcqDealTask,
16
+ AcqDealTaskKind,
17
+ ListStatus,
18
+ ScrapingConfig,
19
+ IcpRubric,
20
+ PipelineConfig,
21
+ ListTelemetry,
22
+ DealDetail
23
+ } from '../../../business/acquisition/types'
24
+ import type { ListProgress } from '../../../business/acquisition/api-schemas'
25
+
26
+ export type {
27
+ AcqList,
28
+ AcqCompany,
29
+ AcqContact,
30
+ AcqDealTask,
31
+ AcqDealTaskKind,
32
+ ListStatus,
33
+ ScrapingConfig,
34
+ IcpRubric,
35
+ PipelineConfig,
36
+ ListTelemetry,
37
+ DealDetail,
38
+ ListProgress
39
+ }
40
+
41
+ // Pagination types
42
+ export interface PaginationParams {
43
+ limit: number
44
+ offset: number
45
+ }
46
+
47
+ export interface PaginatedResult<T> {
48
+ data: T[]
49
+ total: number
50
+ limit: number
51
+ offset: number
52
+ }
53
+
54
+ // List params
55
+ export interface CreateListParams {
56
+ organizationId: string
57
+ name: string
58
+ description?: string
59
+ type?: string
60
+ batchIds?: string[]
61
+ instantlyCampaignId?: string
62
+ status?: ListStatus
63
+ metadata?: Record<string, unknown>
64
+ scrapingConfig?: ScrapingConfig
65
+ icp?: IcpRubric
66
+ pipelineConfig?: PipelineConfig
67
+ }
68
+
69
+ export interface UpdateListParams {
70
+ name?: string
71
+ description?: string
72
+ batchIds?: string[]
73
+ }
74
+
75
+ export interface UpdateListStatusParams {
76
+ organizationId: string
77
+ listId: string
78
+ status: ListStatus
79
+ }
80
+
81
+ // Company params
82
+ export interface CreateCompanyParams {
83
+ organizationId: string
84
+ name: string
85
+ domain?: string
86
+ linkedinUrl?: string
87
+ website?: string
88
+ numEmployees?: number
89
+ foundedYear?: number
90
+ locationCity?: string
91
+ locationState?: string
92
+ category?: string
93
+ source?: string
94
+ batchId?: string
95
+ verticalResearch?: string
96
+ }
97
+
98
+ export interface UpdateCompanyParams {
99
+ name?: string
100
+ domain?: string
101
+ linkedinUrl?: string
102
+ website?: string
103
+ numEmployees?: number
104
+ foundedYear?: number
105
+ locationCity?: string
106
+ locationState?: string
107
+ category?: string
108
+ segment?: string
109
+ pipelineStatus?: Record<string, unknown>
110
+ enrichmentData?: Record<string, unknown>
111
+ source?: string
112
+ batchId?: string
113
+ status?: 'active' | 'invalid'
114
+ verticalResearch?: string | null
115
+ /** Track A: flat qualification score column (null until a scoring rubric is defined) */
116
+ qualificationScore?: number | null
117
+ /** Track A: flat qualification signals jsonb — mirrors the former pipeline_status.qualification shape */
118
+ qualificationSignals?: Record<string, unknown> | null
119
+ /** Track A: key identifying the rubric used for qualification */
120
+ qualificationRubricKey?: string | null
121
+ }
122
+
123
+ export type UpsertCompanyParams = CreateCompanyParams
124
+ // Upsert by domain - uses same fields as create
125
+
126
+ export interface CompanyFilters {
127
+ listId?: string // Filter to companies in a specific list (via acq_list_companies)
128
+ search?: string
129
+ domain?: string
130
+ website?: string
131
+ segment?: string
132
+ category?: string
133
+ pipelineStatus?: Record<string, unknown>
134
+ /** Exclude companies whose pipeline_status contains this value (PostgREST NOT contains) */
135
+ pipelineStatusNot?: Record<string, unknown>
136
+ batchId?: string
137
+ status?: 'active' | 'invalid'
138
+ includeAll?: boolean
139
+ excludeColumns?: Array<'enrichmentData' | 'pipelineStatus'>
140
+ }
141
+
142
+ // Contact params
143
+ export interface CreateContactParams {
144
+ organizationId: string
145
+ email: string
146
+ companyId?: string
147
+ firstName?: string
148
+ lastName?: string
149
+ linkedinUrl?: string
150
+ title?: string
151
+ source?: string
152
+ sourceId?: string
153
+ batchId?: string
154
+ }
155
+
156
+ export interface UpdateContactParams {
157
+ companyId?: string
158
+ emailValid?: 'VALID' | 'INVALID' | 'RISKY' | 'UNKNOWN'
159
+ firstName?: string
160
+ lastName?: string
161
+ linkedinUrl?: string
162
+ title?: string
163
+ headline?: string
164
+ filterReason?: string
165
+ openingLine?: string
166
+ pipelineStatus?: Record<string, unknown>
167
+ enrichmentData?: Record<string, unknown>
168
+ status?: 'active' | 'invalid'
169
+ }
170
+
171
+ export type UpsertContactParams = CreateContactParams
172
+ // Upsert by email - uses same fields as create
173
+
174
+ export interface ContactFilters {
175
+ listId?: string // Filter to contacts in a specific list (via acq_list_members)
176
+ search?: string
177
+ openingLineIsNull?: boolean // Filter to contacts without personalization
178
+ pipelineStatus?: Record<string, unknown>
179
+ batchId?: string
180
+ contactStatus?: 'active' | 'invalid' // Filter by contact status (soft-delete flag)
181
+ }
182
+
183
+ // Deal params (for acq_deals table)
184
+ export interface UpsertDealParams {
185
+ organizationId: string
186
+ /** Contact email — dedupe key together with organization_id */
187
+ contactEmail: string
188
+ /** Optional contact ID for foreign key join */
189
+ contactId?: string
190
+ /** Campaign list that generated this deal (FK to acq_lists) */
191
+ sourceListId?: string
192
+ /** Deal origin: 'instantly', 'referral', 'inbound', 'manual' */
193
+ sourceType?: 'instantly' | 'referral' | 'inbound' | 'manual'
194
+ /** Optional discovery data JSONB to set on upsert */
195
+ discoveryData?: unknown
196
+ /** Optional proposal data JSONB to set on upsert */
197
+ proposalData?: unknown
198
+ }
199
+
200
+ export interface UpdateDiscoveryDataParams {
201
+ organizationId: string
202
+ contactEmail: string
203
+ discoveryData: unknown
204
+ submittedBy?: string
205
+ }
206
+
207
+ export interface UpdateProposalDataParams {
208
+ organizationId: string
209
+ contactEmail: string
210
+ proposalData: unknown
211
+ proposalPdfUrl?: string
212
+ }
213
+
214
+ export interface MarkProposalSentParams {
215
+ organizationId: string
216
+ contactEmail: string
217
+ }
218
+
219
+ export interface MarkProposalReviewedParams {
220
+ organizationId: string
221
+ contactEmail: string
222
+ reviewedBy: string
223
+ proposalData?: unknown
224
+ }
225
+
226
+ export interface UpdateCloseLostReasonParams {
227
+ organizationId: string
228
+ dealId: string
229
+ reason: string
230
+ }
231
+
232
+ export interface UpdateFeesParams {
233
+ organizationId: string
234
+ contactEmail?: string
235
+ dealId?: string
236
+ initialFee?: number
237
+ monthlyFee?: number
238
+ }
239
+
240
+ export interface TransitionItemParams {
241
+ organizationId: string
242
+ dealId: string
243
+ pipelineKey: string
244
+ stageKey: string
245
+ stateKey?: string | null
246
+ reason?: string
247
+ expectedUpdatedAt?: string
248
+ }
249
+
250
+ export interface SetContactNurtureParams {
251
+ organizationId: string
252
+ contactEmail: string
253
+ nurture?: boolean
254
+ }
255
+
256
+ export interface DeactivateContactsByCompanyParams {
257
+ organizationId: string
258
+ companyId: string
259
+ }
260
+
261
+ export interface DeactivateContactsByCompanyResult {
262
+ deactivated: number
263
+ }
264
+
265
+ export interface CancelSchedulesAndHitlByEmailParams {
266
+ organizationId: string
267
+ email: string
268
+ }
269
+
270
+ export interface CancelHitlByDealIdParams {
271
+ organizationId: string
272
+ dealId: string
273
+ }
274
+
275
+ export interface ClearDealFieldsParams {
276
+ organizationId: string
277
+ contactEmail?: string
278
+ dealId?: string
279
+ fields: (
280
+ | 'proposalPdfUrl'
281
+ | 'proposalGeneratedAt'
282
+ | 'initialFee'
283
+ | 'monthlyFee'
284
+ | 'closedLostReason'
285
+ | 'closedLostAt'
286
+ | 'discoveryData'
287
+ | 'discoverySubmittedAt'
288
+ )[]
289
+ }
290
+
291
+ export interface DeleteDealParams {
292
+ organizationId: string
293
+ dealId: string
294
+ }
295
+
296
+ export interface GetDealByIdParams {
297
+ dealId: string
298
+ organizationId: string
299
+ }
300
+
301
+ export interface GetContactByIdParams {
302
+ contactId: string
303
+ organizationId: string
304
+ }
305
+
306
+ export interface GetCompanyByIdParams {
307
+ companyId: string
308
+ organizationId: string
309
+ }
310
+
311
+ // Social monitoring params (acq_social_posts table)
312
+ export interface UpsertSocialPostParams {
313
+ organizationId: string
314
+ platform: string
315
+ platformPostId: string
316
+ authorName: string
317
+ authorUrl?: string | null
318
+ postTitle: string
319
+ postText: string
320
+ postUrl: string
321
+ engagementCount?: number
322
+ commentsCount?: number
323
+ postedAt: string
324
+ metadata?: Record<string, unknown>
325
+ relevanceScore?: number
326
+ matchedKeywords?: string[]
327
+ matchedQuery?: string | null
328
+ initialDraft?: string | null
329
+ finalResponse?: string | null
330
+ sourceCategory?: string | null
331
+ }
332
+
333
+ export interface UpsertSocialPostsParams {
334
+ organizationId: string
335
+ posts: Omit<UpsertSocialPostParams, 'organizationId'>[]
336
+ }
337
+
338
+ export interface UpsertSocialPostsResult {
339
+ inserted: number
340
+ duplicatesSkipped: number
341
+ }
342
+
343
+ export interface AcqDeal {
344
+ id: string
345
+ organizationId: string
346
+ contactEmail: string
347
+ pipelineKey: string
348
+ stageKey?: string | null
349
+ stateKey?: string | null
350
+ discoveryData?: Json | null
351
+ proposalData?: Json | null
352
+ proposalSentAt?: string | null
353
+ proposalPdfUrl?: string | null
354
+ signatureEnvelopeId?: string | null
355
+ sourceListId?: string | null
356
+ sourceType?: string | null
357
+ activityLog: DealActivityEntry[]
358
+ createdAt: Date
359
+ updatedAt: Date
360
+ }
361
+
362
+ export interface DealActivityEntry {
363
+ type: string
364
+ title: string
365
+ description?: string
366
+ payload?: Record<string, unknown>
367
+ occurredAt: string
368
+ }
369
+
370
+ export interface AcqDealNote {
371
+ id: string
372
+ dealId: string
373
+ organizationId: string
374
+ authorUserId: string | null
375
+ body: string
376
+ createdAt: string
377
+ updatedAt: string
378
+ }
379
+
380
+ export interface CreateDealNoteParams {
381
+ organizationId: string
382
+ dealId: string
383
+ body: string
384
+ authorUserId?: string
385
+ }
386
+
387
+ export interface ListDealNotesParams {
388
+ organizationId: string
389
+ dealId: string
390
+ }
391
+
392
+ export interface CreateDealTaskParams {
393
+ organizationId: string
394
+ dealId: string
395
+ title: string
396
+ description?: string | null
397
+ kind?: AcqDealTaskKind
398
+ dueAt?: string | null
399
+ assigneeUserId?: string | null
400
+ createdByUserId?: string | null
401
+ }
402
+
403
+ export interface ListDealTasksParams {
404
+ organizationId: string
405
+ dealId: string
406
+ }
407
+
408
+ export interface ListDealTasksDueParams {
409
+ organizationId: string
410
+ assigneeUserId?: string | null
411
+ /** Window filter: 'overdue' = past due, 'today' = due today only, 'today_and_overdue' (default) = both, 'upcoming' = future */
412
+ window?: 'overdue' | 'today' | 'today_and_overdue' | 'upcoming'
413
+ }
414
+
415
+ export interface CompleteDealTaskParams {
416
+ organizationId: string
417
+ taskId: string
418
+ completedByUserId: string | null
419
+ }
420
+
421
+ export interface RecordDealActivityParams {
422
+ organizationId: string
423
+ dealId: string
424
+ type: string
425
+ title: string
426
+ description?: string
427
+ payload?: Record<string, unknown>
428
+ }
429
+
430
+ export interface SetDealStateKeyParams {
431
+ organizationId: string
432
+ dealId: string
433
+ stateKey: string
434
+ }
435
+
436
+ export interface TransitionDealParams {
437
+ organizationId: string
438
+ dealId: string
439
+ toStage: string
440
+ toState?: string
441
+ }
442
+
443
+ export interface LoadDealParams {
444
+ organizationId: string
445
+ dealId: string
446
+ }
447
+
448
+ // Deal analytics types (for /meta status and platform-status workflow)
449
+
450
+ export interface DealStageSummary {
451
+ stage: string
452
+ count: number
453
+ oldestUpdatedAt: string | null
454
+ newestUpdatedAt: string | null
455
+ }
456
+
457
+ export interface StaleDeal {
458
+ id: string
459
+ contactEmail: string
460
+ stageKey: string
461
+ updatedAt: string
462
+ daysStale: number
463
+ }
464
+
465
+ export interface DealPipelineAnalytics {
466
+ totalDeals: number
467
+ stageSummary: DealStageSummary[]
468
+ staleDeals: StaleDeal[]
469
+ recentActivity: AcqDeal[]
470
+ }
471
+
472
+ export interface DealAnalyticsParams {
473
+ organizationId: string
474
+ recentLimit?: number
475
+ }
476
+
477
+ export interface DealFilters {
478
+ stage?: string
479
+ search?: string
480
+ limit?: number
481
+ offset?: number
482
+ }
483
+
484
+ export interface AddContactsToListParams {
485
+ organizationId: string
486
+ listId: string
487
+ contactIds: string[]
488
+ }
489
+
490
+ export interface AddContactsToListResult {
491
+ added: number
492
+ alreadyExisted: number
493
+ }
494
+
495
+ // List config/progress/executions params
496
+ export interface UpdateListConfigParams {
497
+ organizationId: string
498
+ listId: string
499
+ scrapingConfig?: ScrapingConfig
500
+ icp?: IcpRubric
501
+ pipelineConfig?: PipelineConfig
502
+ }
503
+
504
+ export interface UpdateCompanyStageParams {
505
+ organizationId: string
506
+ listId: string
507
+ companyId: string
508
+ stage: string
509
+ executionId?: string
510
+ }
511
+
512
+ export interface UpdateContactStageParams {
513
+ organizationId: string
514
+ listId: string
515
+ contactId: string
516
+ stage: string
517
+ executionId?: string
518
+ }
519
+
520
+ export interface AddCompaniesToListParams {
521
+ organizationId: string
522
+ listId: string
523
+ companyIds: string[]
524
+ }
525
+
526
+ export interface AddCompaniesToListResult {
527
+ added: number
528
+ alreadyExisted: number
529
+ }
530
+
531
+ export interface RemoveCompaniesFromListParams {
532
+ organizationId: string
533
+ listId: string
534
+ companyIds: string[]
535
+ }
536
+
537
+ export interface RemoveCompaniesFromListResult {
538
+ removed: number
539
+ }
540
+
541
+ export interface RecordListExecutionParams {
542
+ organizationId: string
543
+ listId: string
544
+ executionId: string
545
+ configSnapshot?: Record<string, unknown>
546
+ }
547
+
548
+ export interface ListExecutionSummary {
549
+ executionId: string
550
+ resourceId: string
551
+ status: string
552
+ createdAt: string
553
+ completedAt: string | null
554
+ durationMs: number | null
555
+ }
556
+
557
+ // Bulk import (contacts)
558
+ export interface BulkImportParams {
559
+ organizationId: string
560
+ contacts: CreateContactParams[]
561
+ listId?: string
562
+ }
563
+
564
+ export interface BulkImportResult {
565
+ created: number
566
+ updated: number
567
+ errors: Array<{ email: string; error: string }>
568
+ }
569
+
570
+ // Bulk import (companies)
571
+ export interface BulkImportCompanyEntry {
572
+ name: string
573
+ domain: string
574
+ website?: string
575
+ locationCity?: string
576
+ locationState?: string
577
+ category?: string
578
+ source?: string
579
+ enrichmentData?: Record<string, unknown>
580
+ pipelineStatus?: Record<string, unknown>
581
+ }
582
+
583
+ export interface BulkImportCompaniesParams {
584
+ organizationId: string
585
+ batchId: string
586
+ companies: BulkImportCompanyEntry[]
587
+ }
588
+
589
+ export interface BulkImportCompaniesResult {
590
+ created: number
591
+ skipped: number
592
+ errors: Array<{ companyName: string; error: string }>
593
+ }
594
+
595
+ /**
596
+ * Lead Service interface for acquisition platform tools.
597
+ * Provides CRUD operations for lists, companies, and contacts.
598
+ *
599
+ * Implementation: apps/api/src/acquisition/lead-service.ts (LeadService class)
600
+ *
601
+ * Multi-tenancy: All operations require organizationId for tenant isolation.
602
+ * All queries are filtered by organizationId via RLS policies.
603
+ */
604
+ export interface ILeadService {
605
+ // List operations
606
+ /**
607
+ * Create a new list
608
+ * @see LeadService.createList (apps/api/src/acquisition/lead-service.ts)
609
+ */
610
+ createList(params: CreateListParams): Promise<AcqList>
611
+
612
+ /**
613
+ * Update an existing list
614
+ * @see LeadService.updateList (apps/api/src/acquisition/lead-service.ts)
615
+ */
616
+ updateList(id: string, params: UpdateListParams): Promise<AcqList>
617
+
618
+ /**
619
+ * Delete a list
620
+ * @see LeadService.deleteList (apps/api/src/acquisition/lead-service.ts)
621
+ */
622
+ deleteList(id: string, organizationId: string): Promise<void>
623
+
624
+ /**
625
+ * Add contacts to a list (upsert — idempotent on re-runs)
626
+ * @see LeadService.addContactsToList (apps/api/src/business/acquisition/lead-service.ts)
627
+ */
628
+ addContactsToList(params: AddContactsToListParams): Promise<AddContactsToListResult>
629
+
630
+ /**
631
+ * List all lists for an organization
632
+ * @see LeadService.listLists (apps/api/src/acquisition/lead-service.ts)
633
+ */
634
+ listLists(organizationId: string): Promise<AcqList[]>
635
+
636
+ /**
637
+ * Get a single list by ID.
638
+ */
639
+ getList(id: string, organizationId: string): Promise<AcqList | null>
640
+
641
+ /**
642
+ * Deep-merge patch the jsonb `config` column. Patch keys at any depth
643
+ * replace the corresponding subtree.
644
+ */
645
+ updateListConfig(params: UpdateListConfigParams): Promise<AcqList>
646
+
647
+ /**
648
+ * Add companies to a list via the acq_list_companies junction.
649
+ * Idempotent on (list_id, company_id).
650
+ */
651
+ addCompaniesToList(params: AddCompaniesToListParams): Promise<AddCompaniesToListResult>
652
+
653
+ /**
654
+ * Remove companies from a list (delete junction rows only — company rows untouched).
655
+ */
656
+ removeCompaniesFromList(params: RemoveCompaniesFromListParams): Promise<RemoveCompaniesFromListResult>
657
+
658
+ /**
659
+ * Live org-wide list telemetry — computed on demand from acq_companies
660
+ * and acq_contacts joined through the acq_list_companies / acq_list_members
661
+ * junctions. Replaces the batch-scoped getBatchTelemetry.
662
+ */
663
+ getListsTelemetry(organizationId: string): Promise<ListTelemetry[]>
664
+
665
+ /**
666
+ * On-demand progress aggregation: COUNT(*) FILTER over processing_state flags,
667
+ * keyed by the list's pipeline_config.stages[].key (Decision #4 + #7).
668
+ */
669
+ getListProgress(listId: string, organizationId: string): Promise<ListProgress | null>
670
+
671
+ /**
672
+ * Advance a company row within a list's explicit stage journey.
673
+ */
674
+ updateCompanyStage(params: UpdateCompanyStageParams): Promise<void>
675
+
676
+ /**
677
+ * Advance a contact row within a list's explicit stage journey.
678
+ */
679
+ updateContactStage(params: UpdateContactStageParams): Promise<void>
680
+
681
+ /**
682
+ * Per-list execution history — reads via the feature-owned
683
+ * acq_list_executions junction, joined to execution_logs for details.
684
+ */
685
+ getListExecutions(listId: string, organizationId: string): Promise<ListExecutionSummary[]>
686
+
687
+ /**
688
+ * Write a junction row linking (listId, executionId). Called by the
689
+ * workflow layer at execution start in Step 4. No-op if the row exists.
690
+ */
691
+ recordListExecution(params: RecordListExecutionParams): Promise<void>
692
+
693
+ // Company operations
694
+ /**
695
+ * Create a new company
696
+ * @see LeadService.createCompany (apps/api/src/acquisition/lead-service.ts)
697
+ */
698
+ createCompany(params: CreateCompanyParams): Promise<AcqCompany>
699
+
700
+ /**
701
+ * Update an existing company
702
+ * @see LeadService.updateCompany (apps/api/src/acquisition/lead-service.ts)
703
+ */
704
+ updateCompany(id: string, params: UpdateCompanyParams): Promise<AcqCompany>
705
+
706
+ /**
707
+ * Upsert a company by domain
708
+ * @see LeadService.upsertCompany (apps/api/src/acquisition/lead-service.ts)
709
+ */
710
+ upsertCompany(params: UpsertCompanyParams): Promise<AcqCompany>
711
+
712
+ /**
713
+ * Get a company by ID
714
+ * @see LeadService.getCompany (apps/api/src/acquisition/lead-service.ts)
715
+ */
716
+ getCompany(id: string, organizationId: string): Promise<AcqCompany | null>
717
+
718
+ /**
719
+ * List companies with optional filters
720
+ * @see LeadService.listCompanies (apps/api/src/acquisition/lead-service.ts)
721
+ */
722
+ listCompanies(organizationId: string, filters: CompanyFilters): Promise<AcqCompany[]>
723
+
724
+ /**
725
+ * Delete a company
726
+ * @see LeadService.deleteCompany (apps/api/src/acquisition/lead-service.ts)
727
+ */
728
+ deleteCompany(id: string, organizationId: string): Promise<void>
729
+
730
+ // Contact operations
731
+ /**
732
+ * Create a new contact
733
+ * @see LeadService.createContact (apps/api/src/acquisition/lead-service.ts)
734
+ */
735
+ createContact(params: CreateContactParams): Promise<AcqContact>
736
+
737
+ /**
738
+ * Update an existing contact
739
+ * @see LeadService.updateContact (apps/api/src/acquisition/lead-service.ts)
740
+ */
741
+ updateContact(id: string, params: UpdateContactParams): Promise<AcqContact>
742
+
743
+ /**
744
+ * Upsert a contact by email
745
+ * @see LeadService.upsertContact (apps/api/src/acquisition/lead-service.ts)
746
+ */
747
+ upsertContact(params: UpsertContactParams): Promise<AcqContact>
748
+
749
+ /**
750
+ * Get a contact by ID
751
+ * @see LeadService.getContact (apps/api/src/acquisition/lead-service.ts)
752
+ */
753
+ getContact(id: string, organizationId: string): Promise<AcqContact | null>
754
+
755
+ /**
756
+ * List contacts with pagination and filters
757
+ * @see LeadService.listContacts (apps/api/src/acquisition/lead-service.ts)
758
+ */
759
+ listContacts(
760
+ organizationId: string,
761
+ filters: ContactFilters,
762
+ pagination: PaginationParams
763
+ ): Promise<PaginatedResult<AcqContact>>
764
+
765
+ /**
766
+ * Delete a contact
767
+ * @see LeadService.deleteContact (apps/api/src/acquisition/lead-service.ts)
768
+ */
769
+ deleteContact(id: string, organizationId: string): Promise<void>
770
+
771
+ /**
772
+ * Bulk import contacts
773
+ * @see LeadService.bulkImportContacts (apps/api/src/acquisition/lead-service.ts)
774
+ */
775
+ bulkImportContacts(params: BulkImportParams): Promise<BulkImportResult>
776
+
777
+ /**
778
+ * Bulk import companies with domain dedup (skips existing domains).
779
+ * Inserts in batches using Supabase bulk insert + ON CONFLICT.
780
+ * @see LeadService.bulkImportCompanies (apps/api/src/business/acquisition/lead-service.ts)
781
+ */
782
+ bulkImportCompanies(params: BulkImportCompaniesParams): Promise<BulkImportCompaniesResult>
783
+
784
+ /**
785
+ * Deactivate all active contacts belonging to a company.
786
+ * Used by qualification workflow to cascade company disqualification to contacts.
787
+ */
788
+ deactivateContactsByCompany(params: DeactivateContactsByCompanyParams): Promise<DeactivateContactsByCompanyResult>
789
+
790
+ /**
791
+ * Get a contact by email address
792
+ * Used for looking up existing leads when they reply to outreach
793
+ * @see LeadService.getContactByEmail (apps/api/src/acquisition/lead-service.ts)
794
+ */
795
+ getContactByEmail(email: string, organizationId: string): Promise<AcqContact | null>
796
+
797
+ // Deal operations (acq_deals table)
798
+ /**
799
+ * Upsert a deal by Attio Deal ID
800
+ * Creates or updates acq_deals record linking Attio Deal to Supabase
801
+ * @see LeadService.upsertDeal (apps/api/src/acquisition/lead-service.ts)
802
+ */
803
+ upsertDeal(params: UpsertDealParams): Promise<AcqDeal>
804
+
805
+ /**
806
+ * Get a deal by contact email
807
+ * Used for looking up existing deals when leads book via email (fallback lookup)
808
+ * @see LeadService.getDealByEmail (apps/api/src/acquisition/lead-service.ts)
809
+ */
810
+ getDealByEmail(email: string, organizationId: string): Promise<AcqDeal | null>
811
+
812
+ /**
813
+ * Get a deal by SignatureAPI envelope ID
814
+ * Used by webhook handler to find deal when contract is signed
815
+ * @see LeadService.getDealByEnvelopeId (apps/api/src/acquisition/lead-service.ts)
816
+ */
817
+ getDealByEnvelopeId(envelopeId: string, organizationId: string): Promise<AcqDeal | null>
818
+
819
+ /**
820
+ * Update deal with signature envelope ID
821
+ * Called when proposal is sent via SignatureAPI
822
+ * @see LeadService.updateDealEnvelopeId (apps/api/src/acquisition/lead-service.ts)
823
+ */
824
+ updateDealEnvelopeId(dealId: string, envelopeId: string, organizationId: string): Promise<AcqDeal | null>
825
+
826
+ // Deal-sync operations (mirror deal-sync.ts utilities as server-side methods)
827
+
828
+ getDealById(params: GetDealByIdParams): Promise<AcqDeal | null>
829
+
830
+ getContactById(params: GetContactByIdParams): Promise<AcqContact | null>
831
+
832
+ getCompanyById(params: GetCompanyByIdParams): Promise<AcqCompany | null>
833
+
834
+ updateDiscoveryData(params: UpdateDiscoveryDataParams): Promise<void>
835
+
836
+ updateProposalData(params: UpdateProposalDataParams): Promise<void>
837
+
838
+ markProposalSent(params: MarkProposalSentParams): Promise<void>
839
+
840
+ markProposalReviewed(params: MarkProposalReviewedParams): Promise<void>
841
+
842
+ updateCloseLostReason(params: UpdateCloseLostReasonParams): Promise<void>
843
+
844
+ updateFees(params: UpdateFeesParams): Promise<void>
845
+
846
+ transitionItem(params: TransitionItemParams): Promise<void>
847
+
848
+ setContactNurture(params: SetContactNurtureParams): Promise<void>
849
+
850
+ cancelSchedulesAndHitlByEmail(
851
+ params: CancelSchedulesAndHitlByEmailParams
852
+ ): Promise<{ schedulesCancelled: number; hitlDeleted: number }>
853
+
854
+ cancelHitlByDealId(params: CancelHitlByDealIdParams): Promise<{ hitlDeleted: number }>
855
+
856
+ clearDealFields(params: ClearDealFieldsParams): Promise<void>
857
+
858
+ deleteDeal(params: DeleteDealParams): Promise<void>
859
+
860
+ listDeals(organizationId: string, filters?: DealFilters): Promise<AcqDeal[]>
861
+
862
+ getDealPipelineAnalytics(organizationId: string, recentLimit?: number): Promise<DealPipelineAnalytics>
863
+
864
+ /**
865
+ * Deep-merge enrichment data into a company or contact record.
866
+ * Merges per source key rather than wholesale replacing the JSONB object.
867
+ * @see LeadService.mergeEnrichmentData (apps/api/src/business/acquisition/lead-service.ts)
868
+ */
869
+ mergeEnrichmentData(
870
+ id: string,
871
+ orgId: string,
872
+ table: 'acq_companies' | 'acq_contacts',
873
+ data: Record<string, unknown>
874
+ ): Promise<void>
875
+
876
+ // Deal note operations (acq_deal_notes table)
877
+ /**
878
+ * Create a human-authored note on a deal
879
+ * @see LeadService.createDealNote (apps/api/src/business/acquisition/lead-service.ts)
880
+ */
881
+ createDealNote(params: CreateDealNoteParams): Promise<AcqDealNote>
882
+
883
+ /**
884
+ * List notes for a deal, ordered by created_at DESC
885
+ * @see LeadService.listDealNotes (apps/api/src/business/acquisition/lead-service.ts)
886
+ */
887
+ listDealNotes(params: ListDealNotesParams): Promise<AcqDealNote[]>
888
+
889
+ // Deal task operations (acq_deal_tasks table)
890
+ /**
891
+ * Creates a new task attached to a deal.
892
+ * @see LeadService.createDealTask (apps/api/src/business/acquisition/lead-service.ts)
893
+ */
894
+ createDealTask(params: CreateDealTaskParams): Promise<AcqDealTask>
895
+
896
+ /**
897
+ * Lists all tasks for a given deal, ordered by due date.
898
+ * @see LeadService.listDealTasks (apps/api/src/business/acquisition/lead-service.ts)
899
+ */
900
+ listDealTasks(params: ListDealTasksParams): Promise<AcqDealTask[]>
901
+
902
+ /**
903
+ * Lists open (uncompleted) tasks within a date window across all deals.
904
+ * @see LeadService.listDealTasksDue (apps/api/src/business/acquisition/lead-service.ts)
905
+ */
906
+ listDealTasksDue(params: ListDealTasksDueParams): Promise<AcqDealTask[]>
907
+
908
+ /**
909
+ * Marks a task as completed.
910
+ * @see LeadService.completeDealTask (apps/api/src/business/acquisition/lead-service.ts)
911
+ */
912
+ completeDealTask(params: CompleteDealTaskParams): Promise<AcqDealTask>
913
+
914
+ /**
915
+ * Record a deal activity entry by deal ID.
916
+ * Generic method for any activity type — used by SDK workflows.
917
+ * @see LeadService.recordDealActivity (apps/api/src/business/acquisition/lead-service.ts)
918
+ */
919
+ recordDealActivity(params: RecordDealActivityParams): Promise<void>
920
+
921
+ /**
922
+ * Update the state_key on an acq_deals row.
923
+ * @see LeadService.setDealStateKey (apps/api/src/business/acquisition/lead-service.ts)
924
+ */
925
+ setDealStateKey(params: SetDealStateKeyParams): Promise<{ ok: true }>
926
+
927
+ /**
928
+ * Transition a deal to a new stage, resolving pipeline_key automatically.
929
+ * @see LeadService.transitionDeal (apps/api/src/business/acquisition/lead-service.ts)
930
+ */
931
+ transitionDeal(params: TransitionDealParams): Promise<{ deal: AcqDeal }>
932
+
933
+ /**
934
+ * Load a deal with its joined contact and company data.
935
+ * @see LeadService.loadDeal (apps/api/src/business/acquisition/lead-service.ts)
936
+ */
937
+ loadDeal(params: LoadDealParams): Promise<DealDetail | null>
938
+
939
+ // Social monitoring operations (acq_social_posts table)
940
+ /**
941
+ * Bulk upsert social posts (deduplicate by platform + platform_post_id)
942
+ * @see LeadService.upsertSocialPosts (apps/api/src/business/acquisition/lead-service.ts)
943
+ */
944
+ upsertSocialPosts(params: UpsertSocialPostsParams): Promise<UpsertSocialPostsResult>
945
+ }