@startsimpli/funnels 0.4.6 → 0.4.8

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 (46) hide show
  1. package/package.json +1 -1
  2. package/src/api/client.test.ts +76 -76
  3. package/src/api/client.ts +16 -16
  4. package/src/components/FilterRuleEditor/FilterRuleEditor.stories.tsx +24 -24
  5. package/src/components/FilterRuleEditor/FilterRuleEditor.test.tsx +39 -39
  6. package/src/components/FilterRuleEditor/FilterRuleEditor.tsx +1 -1
  7. package/src/components/FilterRuleEditor/RuleRow.tsx +8 -8
  8. package/src/components/FunnelCard/FunnelCard.stories.tsx +118 -118
  9. package/src/components/FunnelCard/FunnelCard.test.ts +48 -48
  10. package/src/components/FunnelCard/FunnelCard.test.tsx +40 -40
  11. package/src/components/FunnelCard/FunnelCard.tsx +3 -3
  12. package/src/components/FunnelPreview/FunnelPreview.test.tsx +63 -63
  13. package/src/components/FunnelPreview/FunnelPreview.tsx +11 -11
  14. package/src/components/FunnelPreview/StageBreakdown.tsx +5 -5
  15. package/src/components/FunnelPreview/example.tsx +43 -43
  16. package/src/components/FunnelRunHistory/FunnelRunHistory.stories.tsx +70 -70
  17. package/src/components/FunnelRunHistory/FunnelRunHistory.test.tsx +63 -63
  18. package/src/components/FunnelRunHistory/FunnelRunHistory.tsx +12 -12
  19. package/src/components/FunnelRunHistory/RunDetailsModal.tsx +5 -5
  20. package/src/components/FunnelRunHistory/RunFilters.tsx +8 -8
  21. package/src/components/FunnelRunHistory/RunRow.tsx +7 -7
  22. package/src/components/FunnelRunHistory/StageBreakdownList.tsx +9 -9
  23. package/src/components/FunnelRunHistory/types.ts +6 -6
  24. package/src/components/FunnelStageBuilder/FunnelStageBuilder.stories.tsx +41 -41
  25. package/src/components/FunnelStageBuilder/FunnelStageBuilder.test.tsx +6 -6
  26. package/src/components/FunnelStageBuilder/FunnelStageBuilder.tsx +5 -5
  27. package/src/components/FunnelStageBuilder/StageActions.test.tsx +7 -7
  28. package/src/components/FunnelStageBuilder/StageActions.tsx +4 -4
  29. package/src/components/FunnelStageBuilder/StageCard.tsx +3 -3
  30. package/src/components/FunnelStageBuilder/StageForm.tsx +14 -14
  31. package/src/components/FunnelVisualFlow/FunnelVisualFlow.stories.tsx +42 -42
  32. package/src/components/FunnelVisualFlow/FunnelVisualFlow.test.tsx +31 -31
  33. package/src/components/FunnelVisualFlow/FunnelVisualFlow.tsx +4 -4
  34. package/src/components/FunnelVisualFlow/StageNode.tsx +8 -8
  35. package/src/components/FunnelVisualFlow/example.tsx +7 -7
  36. package/src/core/engine.test.ts +271 -271
  37. package/src/core/engine.ts +60 -60
  38. package/src/core/evaluator.example.ts +31 -31
  39. package/src/core/evaluator.test.ts +92 -92
  40. package/src/core/evaluator.ts +23 -23
  41. package/src/core/field-resolver.example.ts +1 -1
  42. package/src/core/field-resolver.test.ts +4 -4
  43. package/src/store/create-funnel-store.test.ts +32 -32
  44. package/src/store/create-funnel-store.ts +3 -3
  45. package/src/store/types.ts +2 -2
  46. package/src/types/index.ts +64 -64
@@ -22,37 +22,37 @@ function createFunnel(overrides?: Partial<Funnel>): Funnel {
22
22
  name: 'Test Funnel',
23
23
  description: 'Test funnel description',
24
24
  status: 'active',
25
- input_type: 'any',
25
+ inputType: 'any',
26
26
  stages: [
27
27
  {
28
28
  id: 'stage-1',
29
29
  order: 0,
30
30
  name: 'Stage 1',
31
31
  description: 'First stage',
32
- filter_logic: 'AND',
32
+ filterLogic: 'AND',
33
33
  rules: [
34
- { field_path: 'name', operator: 'eq', value: 'test' }
34
+ { fieldPath: 'name', operator: 'eq', value: 'test' }
35
35
  ],
36
- match_action: 'continue',
37
- no_match_action: 'exclude',
36
+ matchAction: 'continue',
37
+ noMatchAction: 'exclude',
38
38
  },
39
39
  {
40
40
  id: 'stage-2',
41
41
  order: 1,
42
42
  name: 'Stage 2',
43
43
  description: 'Second stage',
44
- filter_logic: 'OR',
44
+ filterLogic: 'OR',
45
45
  rules: [
46
- { field_path: 'value', operator: 'gt', value: 100 },
47
- { field_path: 'type', operator: 'in', value: ['A', 'B'] },
48
- { field_path: 'active', operator: 'is_true', value: null },
46
+ { fieldPath: 'value', operator: 'gt', value: 100 },
47
+ { fieldPath: 'type', operator: 'in', value: ['A', 'B'] },
48
+ { fieldPath: 'active', operator: 'is_true', value: null },
49
49
  ],
50
- match_action: 'output',
51
- no_match_action: 'exclude',
50
+ matchAction: 'output',
51
+ noMatchAction: 'exclude',
52
52
  },
53
53
  ],
54
- created_at: '2024-01-01T00:00:00Z',
55
- updated_at: '2024-01-01T00:00:00Z',
54
+ createdAt: '2024-01-01T00:00:00Z',
55
+ updatedAt: '2024-01-01T00:00:00Z',
56
56
  ...overrides,
57
57
  };
58
58
  }
@@ -60,17 +60,17 @@ function createFunnel(overrides?: Partial<Funnel>): Funnel {
60
60
  function createFunnelRun(overrides?: Partial<FunnelRun>): FunnelRun {
61
61
  return {
62
62
  id: 'run-1',
63
- funnel_id: 'funnel-1',
63
+ funnelId: 'funnel-1',
64
64
  status: 'completed',
65
- trigger_type: 'manual',
66
- started_at: '2024-01-01T00:00:00Z',
67
- completed_at: '2024-01-01T00:01:00Z',
68
- duration_ms: 60000,
69
- total_input: 83061,
70
- total_matched: 235,
71
- total_excluded: 82826,
72
- total_tagged: 235,
73
- stage_stats: {},
65
+ triggerType: 'manual',
66
+ startedAt: '2024-01-01T00:00:00Z',
67
+ completedAt: '2024-01-01T00:01:00Z',
68
+ durationMs: 60000,
69
+ totalInput: 83061,
70
+ totalMatched: 235,
71
+ totalExcluded: 82826,
72
+ totalTagged: 235,
73
+ stageStats: {},
74
74
  ...overrides,
75
75
  };
76
76
  }
@@ -167,10 +167,10 @@ describe('FunnelCard', () => {
167
167
  id: 'stage-1',
168
168
  order: 0,
169
169
  name: 'Only Stage',
170
- filter_logic: 'AND',
171
- rules: [{ field_path: 'x', operator: 'eq', value: 1 }],
172
- match_action: 'output',
173
- no_match_action: 'exclude',
170
+ filterLogic: 'AND',
171
+ rules: [{ fieldPath: 'x', operator: 'eq', value: 1 }],
172
+ matchAction: 'output',
173
+ noMatchAction: 'exclude',
174
174
  },
175
175
  ],
176
176
  });
@@ -185,10 +185,10 @@ describe('FunnelCard', () => {
185
185
  id: `stage-${i}`,
186
186
  order: i,
187
187
  name: `Stage ${i + 1}`,
188
- filter_logic: 'AND' as const,
189
- rules: [{ field_path: 'x', operator: 'eq' as const, value: i }],
190
- match_action: 'continue' as const,
191
- no_match_action: 'exclude' as const,
188
+ filterLogic: 'AND' as const,
189
+ rules: [{ fieldPath: 'x', operator: 'eq' as const, value: i }],
190
+ matchAction: 'continue' as const,
191
+ noMatchAction: 'exclude' as const,
192
192
  }));
193
193
  const funnel = createFunnel({ stages });
194
194
  render(<FunnelCard funnel={funnel} />);
@@ -203,10 +203,10 @@ describe('FunnelCard', () => {
203
203
  id: `stage-${i}`,
204
204
  order: i,
205
205
  name: `Stage ${i + 1}`,
206
- filter_logic: 'AND' as const,
207
- rules: [{ field_path: 'x', operator: 'eq' as const, value: i }],
208
- match_action: 'continue' as const,
209
- no_match_action: 'exclude' as const,
206
+ filterLogic: 'AND' as const,
207
+ rules: [{ fieldPath: 'x', operator: 'eq' as const, value: i }],
208
+ matchAction: 'continue' as const,
209
+ noMatchAction: 'exclude' as const,
210
210
  }));
211
211
  const funnel = createFunnel({ stages });
212
212
  render(<FunnelCard funnel={funnel} />);
@@ -289,8 +289,8 @@ describe('FunnelCard', () => {
289
289
  it('handles zero matched entities', () => {
290
290
  const funnel = createFunnel();
291
291
  const latestRun = createFunnelRun({
292
- total_matched: 0,
293
- total_excluded: 100,
292
+ totalMatched: 0,
293
+ totalExcluded: 100,
294
294
  });
295
295
  render(<FunnelCard funnel={funnel} latestRun={latestRun} />);
296
296
 
@@ -301,9 +301,9 @@ describe('FunnelCard', () => {
301
301
  it('handles zero input entities', () => {
302
302
  const funnel = createFunnel();
303
303
  const latestRun = createFunnelRun({
304
- total_input: 0,
305
- total_matched: 0,
306
- total_excluded: 0,
304
+ totalInput: 0,
305
+ totalMatched: 0,
306
+ totalExcluded: 0,
307
307
  });
308
308
  render(<FunnelCard funnel={funnel} latestRun={latestRun} />);
309
309
 
@@ -73,9 +73,9 @@ export function FunnelCard({
73
73
  }: FunnelCardProps) {
74
74
  // Extract stats from latest run or use zeros
75
75
  const stats = latestRun ? {
76
- input: latestRun.total_input,
77
- matched: latestRun.total_matched,
78
- excluded: latestRun.total_excluded,
76
+ input: latestRun.totalInput,
77
+ matched: latestRun.totalMatched,
78
+ excluded: latestRun.totalExcluded,
79
79
  } : {
80
80
  input: 0,
81
81
  matched: 0,
@@ -20,46 +20,46 @@ const mockFunnel: Funnel = {
20
20
  name: 'Test Funnel',
21
21
  description: 'A test funnel',
22
22
  status: 'active',
23
- input_type: 'contacts',
23
+ inputType: 'contacts',
24
24
  stages: [
25
25
  {
26
26
  id: 'stage-1',
27
27
  order: 0,
28
28
  name: 'High Score',
29
- filter_logic: 'AND',
29
+ filterLogic: 'AND',
30
30
  rules: [
31
31
  {
32
- field_path: 'score',
32
+ fieldPath: 'score',
33
33
  operator: 'gte',
34
34
  value: 80,
35
35
  },
36
36
  ],
37
- match_action: 'continue',
38
- no_match_action: 'exclude',
37
+ matchAction: 'continue',
38
+ noMatchAction: 'exclude',
39
39
  },
40
40
  {
41
41
  id: 'stage-2',
42
42
  order: 1,
43
43
  name: 'Series A or Seed',
44
- filter_logic: 'OR',
44
+ filterLogic: 'OR',
45
45
  rules: [
46
46
  {
47
- field_path: 'firm.stage',
47
+ fieldPath: 'firm.stage',
48
48
  operator: 'eq',
49
49
  value: 'Series A',
50
50
  },
51
51
  {
52
- field_path: 'firm.stage',
52
+ fieldPath: 'firm.stage',
53
53
  operator: 'eq',
54
54
  value: 'Seed',
55
55
  },
56
56
  ],
57
- match_action: 'output',
58
- no_match_action: 'exclude',
57
+ matchAction: 'output',
58
+ noMatchAction: 'exclude',
59
59
  },
60
60
  ],
61
- created_at: new Date().toISOString(),
62
- updated_at: new Date().toISOString(),
61
+ createdAt: new Date().toISOString(),
62
+ updatedAt: new Date().toISOString(),
63
63
  };
64
64
 
65
65
  describe('FunnelPreview', () => {
@@ -69,10 +69,10 @@ describe('FunnelPreview', () => {
69
69
  const engine = new FunnelEngine();
70
70
  const result = engine.execute(mockFunnel, mockEntities);
71
71
 
72
- expect(result.total_input).toBe(5);
73
- expect(result.total_matched).toBeGreaterThan(0);
74
- expect(result.total_excluded).toBeGreaterThan(0);
75
- expect(result.matched.length).toBe(result.total_matched);
72
+ expect(result.totalInput).toBe(5);
73
+ expect(result.totalMatched).toBeGreaterThan(0);
74
+ expect(result.totalExcluded).toBeGreaterThan(0);
75
+ expect(result.matched.length).toBe(result.totalMatched);
76
76
  });
77
77
 
78
78
  it('should handle empty entities', async () => {
@@ -80,9 +80,9 @@ describe('FunnelPreview', () => {
80
80
  const engine = new FunnelEngine();
81
81
  const result = engine.execute(mockFunnel, []);
82
82
 
83
- expect(result.total_input).toBe(0);
84
- expect(result.total_matched).toBe(0);
85
- expect(result.total_excluded).toBe(0);
83
+ expect(result.totalInput).toBe(0);
84
+ expect(result.totalMatched).toBe(0);
85
+ expect(result.totalExcluded).toBe(0);
86
86
  expect(result.matched).toEqual([]);
87
87
  });
88
88
 
@@ -97,8 +97,8 @@ describe('FunnelPreview', () => {
97
97
  const result = engine.execute(emptyFunnel, mockEntities);
98
98
 
99
99
  // With no stages, all entities should match
100
- expect(result.total_matched).toBe(5);
101
- expect(result.total_excluded).toBe(0);
100
+ expect(result.totalMatched).toBe(5);
101
+ expect(result.totalExcluded).toBe(0);
102
102
  });
103
103
  });
104
104
 
@@ -109,11 +109,11 @@ describe('FunnelPreview', () => {
109
109
  const result = engine.execute(mockFunnel, mockEntities);
110
110
 
111
111
  // Should have stats for both stages
112
- expect(result.stage_stats['stage-1']).toBeDefined();
113
- expect(result.stage_stats['stage-2']).toBeDefined();
112
+ expect(result.stageStats['stage-1']).toBeDefined();
113
+ expect(result.stageStats['stage-2']).toBeDefined();
114
114
 
115
115
  // Stage 1 should process all entities
116
- expect(result.stage_stats['stage-1'].input_count).toBe(5);
116
+ expect(result.stageStats['stage-1'].inputCount).toBe(5);
117
117
  });
118
118
 
119
119
  it('should count exclusions per stage', async () => {
@@ -122,12 +122,12 @@ describe('FunnelPreview', () => {
122
122
  const result = engine.execute(mockFunnel, mockEntities);
123
123
 
124
124
  // Sum of excluded across all stages should equal total excluded
125
- const totalExcludedAcrossStages = Object.values(result.stage_stats).reduce(
126
- (sum, stats) => sum + stats.excluded_count,
125
+ const totalExcludedAcrossStages = Object.values(result.stageStats).reduce(
126
+ (sum, stats) => sum + stats.excludedCount,
127
127
  0
128
128
  );
129
129
 
130
- expect(totalExcludedAcrossStages).toBeLessThanOrEqual(result.total_excluded);
130
+ expect(totalExcludedAcrossStages).toBeLessThanOrEqual(result.totalExcluded);
131
131
  });
132
132
  });
133
133
 
@@ -137,9 +137,9 @@ describe('FunnelPreview', () => {
137
137
  const engine = new FunnelEngine();
138
138
  const result = engine.execute(mockFunnel, mockEntities);
139
139
 
140
- const total = result.total_matched + result.total_excluded;
140
+ const total = result.totalMatched + result.totalExcluded;
141
141
  const expectedPercentage =
142
- total > 0 ? Math.round((result.total_matched / total) * 100) : 0;
142
+ total > 0 ? Math.round((result.totalMatched / total) * 100) : 0;
143
143
 
144
144
  expect(expectedPercentage).toBeGreaterThanOrEqual(0);
145
145
  expect(expectedPercentage).toBeLessThanOrEqual(100);
@@ -166,28 +166,28 @@ describe('FunnelPreview', () => {
166
166
  id: 'stage-1',
167
167
  order: 0,
168
168
  name: 'Stage 1',
169
- filter_logic: 'AND',
170
- rules: [{ field_path: 'score', operator: 'gte', value: 80 }],
171
- match_action: 'continue',
172
- no_match_action: 'exclude',
169
+ filterLogic: 'AND',
170
+ rules: [{ fieldPath: 'score', operator: 'gte', value: 80 }],
171
+ matchAction: 'continue',
172
+ noMatchAction: 'exclude',
173
173
  },
174
174
  {
175
175
  id: 'stage-2',
176
176
  order: 1,
177
177
  name: 'Stage 2',
178
- filter_logic: 'AND',
179
- rules: [{ field_path: 'score', operator: 'lte', value: 90 }],
180
- match_action: 'continue',
181
- no_match_action: 'exclude',
178
+ filterLogic: 'AND',
179
+ rules: [{ fieldPath: 'score', operator: 'lte', value: 90 }],
180
+ matchAction: 'continue',
181
+ noMatchAction: 'exclude',
182
182
  },
183
183
  {
184
184
  id: 'stage-3',
185
185
  order: 2,
186
186
  name: 'Stage 3',
187
- filter_logic: 'AND',
188
- rules: [{ field_path: 'firm.stage', operator: 'eq', value: 'Series A' }],
189
- match_action: 'output',
190
- no_match_action: 'exclude',
187
+ filterLogic: 'AND',
188
+ rules: [{ fieldPath: 'firm.stage', operator: 'eq', value: 'Series A' }],
189
+ matchAction: 'output',
190
+ noMatchAction: 'exclude',
191
191
  },
192
192
  ],
193
193
  };
@@ -197,10 +197,10 @@ describe('FunnelPreview', () => {
197
197
  const result = engine.execute(multiFunnel, mockEntities);
198
198
 
199
199
  // Should have stats for all 3 stages
200
- expect(Object.keys(result.stage_stats).length).toBe(3);
201
- expect(result.stage_stats['stage-1']).toBeDefined();
202
- expect(result.stage_stats['stage-2']).toBeDefined();
203
- expect(result.stage_stats['stage-3']).toBeDefined();
200
+ expect(Object.keys(result.stageStats).length).toBe(3);
201
+ expect(result.stageStats['stage-1']).toBeDefined();
202
+ expect(result.stageStats['stage-2']).toBeDefined();
203
+ expect(result.stageStats['stage-3']).toBeDefined();
204
204
  });
205
205
 
206
206
  it('should track remaining entities correctly', async () => {
@@ -209,10 +209,10 @@ describe('FunnelPreview', () => {
209
209
  const result = engine.execute(mockFunnel, mockEntities);
210
210
 
211
211
  // For each stage, remaining = input - excluded
212
- Object.values(result.stage_stats).forEach((stats) => {
213
- const remaining = stats.input_count - stats.excluded_count;
212
+ Object.values(result.stageStats).forEach((stats) => {
213
+ const remaining = stats.inputCount - stats.excludedCount;
214
214
  expect(remaining).toBeGreaterThanOrEqual(0);
215
- expect(remaining).toBeLessThanOrEqual(stats.input_count);
215
+ expect(remaining).toBeLessThanOrEqual(stats.inputCount);
216
216
  });
217
217
  });
218
218
  });
@@ -223,9 +223,9 @@ describe('FunnelPreview', () => {
223
223
  const engine = new FunnelEngine();
224
224
  const result = engine.execute(mockFunnel, []);
225
225
 
226
- expect(result.total_input).toBe(0);
227
- expect(result.total_matched).toBe(0);
228
- expect(result.total_excluded).toBe(0);
226
+ expect(result.totalInput).toBe(0);
227
+ expect(result.totalMatched).toBe(0);
228
+ expect(result.totalExcluded).toBe(0);
229
229
  });
230
230
 
231
231
  it('should handle 100 entities', async () => {
@@ -239,8 +239,8 @@ describe('FunnelPreview', () => {
239
239
  const engine = new FunnelEngine();
240
240
  const result = engine.execute(mockFunnel, largeEntities);
241
241
 
242
- expect(result.total_input).toBe(100);
243
- expect(result.total_matched + result.total_excluded).toBe(100);
242
+ expect(result.totalInput).toBe(100);
243
+ expect(result.totalMatched + result.totalExcluded).toBe(100);
244
244
  });
245
245
 
246
246
  it('should handle funnel with no rules', async () => {
@@ -251,10 +251,10 @@ describe('FunnelPreview', () => {
251
251
  id: 'stage-1',
252
252
  order: 0,
253
253
  name: 'Pass All',
254
- filter_logic: 'AND',
254
+ filterLogic: 'AND',
255
255
  rules: [],
256
- match_action: 'output',
257
- no_match_action: 'exclude',
256
+ matchAction: 'output',
257
+ noMatchAction: 'exclude',
258
258
  },
259
259
  ],
260
260
  };
@@ -264,8 +264,8 @@ describe('FunnelPreview', () => {
264
264
  const result = engine.execute(noRulesFunnel, mockEntities);
265
265
 
266
266
  // With no rules, all entities should match
267
- expect(result.total_matched).toBe(5);
268
- expect(result.total_excluded).toBe(0);
267
+ expect(result.totalMatched).toBe(5);
268
+ expect(result.totalExcluded).toBe(0);
269
269
  });
270
270
 
271
271
  it('should handle custom evaluator', async () => {
@@ -276,11 +276,11 @@ describe('FunnelPreview', () => {
276
276
  id: 'stage-1',
277
277
  order: 0,
278
278
  name: 'Custom',
279
- filter_logic: 'AND',
279
+ filterLogic: 'AND',
280
280
  rules: [],
281
- match_action: 'output',
282
- no_match_action: 'exclude',
283
- custom_evaluator: (entity: any) => entity.score > 85,
281
+ matchAction: 'output',
282
+ noMatchAction: 'exclude',
283
+ customEvaluator: (entity: any) => entity.score > 85,
284
284
  },
285
285
  ],
286
286
  };
@@ -290,7 +290,7 @@ describe('FunnelPreview', () => {
290
290
  const result = engine.execute(customFunnel, mockEntities);
291
291
 
292
292
  // Should match entities with score > 85
293
- expect(result.total_matched).toBe(3); // Jane(92), Alice(88), Charlie(95)
293
+ expect(result.totalMatched).toBe(3); // Jane(92), Alice(88), Charlie(95)
294
294
  });
295
295
  });
296
296
 
@@ -76,12 +76,12 @@ function convertToPreviewResult(
76
76
  execResult: ExecutionResult,
77
77
  maxEntities: number = 10
78
78
  ): PreviewResult {
79
- const { matched, total_matched, total_excluded, stage_stats } = execResult;
79
+ const { matched, totalMatched, totalExcluded, stageStats } = execResult;
80
80
 
81
81
  // Calculate match percentage
82
- const total = total_matched + total_excluded;
82
+ const total = totalMatched + totalExcluded;
83
83
  const matchPercentage =
84
- total > 0 ? Math.round((total_matched / total) * 100) : 0;
84
+ total > 0 ? Math.round((totalMatched / total) * 100) : 0;
85
85
 
86
86
  // Get preview entities (first N matched)
87
87
  const previewEntities = matched.slice(0, maxEntities).map((r) => r.entity);
@@ -89,19 +89,19 @@ function convertToPreviewResult(
89
89
  // Convert stage stats to preview format
90
90
  const previewStageStats: Record<string, StagePreviewStats> = {};
91
91
 
92
- Object.entries(stage_stats).forEach(([stageId, stats]) => {
92
+ Object.entries(stageStats).forEach(([stageId, stats]) => {
93
93
  previewStageStats[stageId] = {
94
- stage_id: stats.stage_id,
95
- stage_name: stats.stage_name,
96
- input_count: stats.input_count,
97
- excluded_count: stats.excluded_count,
98
- remaining_count: stats.input_count - stats.excluded_count,
94
+ stageId: stats.stageId,
95
+ stageName: stats.stageName,
96
+ inputCount: stats.inputCount,
97
+ excludedCount: stats.excludedCount,
98
+ remaining_count: stats.inputCount - stats.excludedCount,
99
99
  };
100
100
  });
101
101
 
102
102
  return {
103
- totalMatched: total_matched,
104
- totalExcluded: total_excluded,
103
+ totalMatched: totalMatched,
104
+ totalExcluded: totalExcluded,
105
105
  matchPercentage,
106
106
  previewEntities,
107
107
  stageStats: previewStageStats,
@@ -19,10 +19,10 @@
19
19
  import type { StageStats } from '../../types';
20
20
 
21
21
  export interface StagePreviewStats {
22
- stage_id: string;
23
- stage_name: string;
24
- input_count: number;
25
- excluded_count: number;
22
+ stageId: string;
23
+ stageName: string;
24
+ inputCount: number;
25
+ excludedCount: number;
26
26
  remaining_count: number;
27
27
  }
28
28
 
@@ -52,7 +52,7 @@ export function StageBreakdown({
52
52
  if (!stats) return null;
53
53
 
54
54
  const isLast = index === sortedStages.length - 1;
55
- const excludedCount = stats.excluded_count;
55
+ const excludedCount = stats.excludedCount;
56
56
  const remainingCount = stats.remaining_count;
57
57
 
58
58
  return (
@@ -17,33 +17,33 @@ const investorFunnel: Funnel = {
17
17
  name: 'Investor Qualification',
18
18
  description: 'Filter investors by firm stage and check size',
19
19
  status: 'active',
20
- input_type: 'contacts',
20
+ inputType: 'contacts',
21
21
  stages: [
22
22
  {
23
23
  id: 'stage-1',
24
24
  order: 0,
25
25
  name: 'Series A or Later',
26
- filter_logic: 'OR',
26
+ filterLogic: 'OR',
27
27
  rules: [
28
- { field_path: 'firm.stage', operator: 'eq', value: 'Series A' },
29
- { field_path: 'firm.stage', operator: 'eq', value: 'Series B' },
30
- { field_path: 'firm.stage', operator: 'eq', value: 'Series C' },
28
+ { fieldPath: 'firm.stage', operator: 'eq', value: 'Series A' },
29
+ { fieldPath: 'firm.stage', operator: 'eq', value: 'Series B' },
30
+ { fieldPath: 'firm.stage', operator: 'eq', value: 'Series C' },
31
31
  ],
32
- match_action: 'continue',
33
- no_match_action: 'exclude',
32
+ matchAction: 'continue',
33
+ noMatchAction: 'exclude',
34
34
  },
35
35
  {
36
36
  id: 'stage-2',
37
37
  order: 1,
38
38
  name: 'High ICP Score',
39
- filter_logic: 'AND',
40
- rules: [{ field_path: 'icp_score', operator: 'gte', value: 80 }],
41
- match_action: 'output',
42
- no_match_action: 'exclude',
39
+ filterLogic: 'AND',
40
+ rules: [{ fieldPath: 'icp_score', operator: 'gte', value: 80 }],
41
+ matchAction: 'output',
42
+ noMatchAction: 'exclude',
43
43
  },
44
44
  ],
45
- created_at: new Date().toISOString(),
46
- updated_at: new Date().toISOString(),
45
+ createdAt: new Date().toISOString(),
46
+ updatedAt: new Date().toISOString(),
47
47
  };
48
48
 
49
49
  const sampleInvestors = [
@@ -112,32 +112,32 @@ const recipeFunnel: Funnel = {
112
112
  name: 'Recipe Recommendations',
113
113
  description: 'Filter recipes by dietary restrictions and difficulty',
114
114
  status: 'active',
115
- input_type: 'any',
115
+ inputType: 'any',
116
116
  stages: [
117
117
  {
118
118
  id: 'dietary',
119
119
  order: 0,
120
120
  name: 'Vegetarian',
121
- filter_logic: 'AND',
122
- rules: [{ field_path: 'tags', operator: 'has_tag', value: 'vegetarian' }],
123
- match_action: 'continue',
124
- no_match_action: 'exclude',
121
+ filterLogic: 'AND',
122
+ rules: [{ fieldPath: 'tags', operator: 'has_tag', value: 'vegetarian' }],
123
+ matchAction: 'continue',
124
+ noMatchAction: 'exclude',
125
125
  },
126
126
  {
127
127
  id: 'difficulty',
128
128
  order: 1,
129
129
  name: 'Easy to Moderate',
130
- filter_logic: 'OR',
130
+ filterLogic: 'OR',
131
131
  rules: [
132
- { field_path: 'difficulty', operator: 'eq', value: 'easy' },
133
- { field_path: 'difficulty', operator: 'eq', value: 'moderate' },
132
+ { fieldPath: 'difficulty', operator: 'eq', value: 'easy' },
133
+ { fieldPath: 'difficulty', operator: 'eq', value: 'moderate' },
134
134
  ],
135
- match_action: 'output',
136
- no_match_action: 'exclude',
135
+ matchAction: 'output',
136
+ noMatchAction: 'exclude',
137
137
  },
138
138
  ],
139
- created_at: new Date().toISOString(),
140
- updated_at: new Date().toISOString(),
139
+ createdAt: new Date().toISOString(),
140
+ updatedAt: new Date().toISOString(),
141
141
  };
142
142
 
143
143
  const sampleRecipes = [
@@ -198,43 +198,43 @@ const leadFunnel: Funnel = {
198
198
  name: 'Qualified Leads',
199
199
  description: 'Score and filter leads based on company size and engagement',
200
200
  status: 'active',
201
- input_type: 'any',
201
+ inputType: 'any',
202
202
  stages: [
203
203
  {
204
204
  id: 'company-size',
205
205
  order: 0,
206
206
  name: 'Mid-Market and Above',
207
- filter_logic: 'AND',
208
- rules: [{ field_path: 'company.employees', operator: 'gte', value: 50 }],
209
- match_action: 'continue',
210
- no_match_action: 'exclude',
207
+ filterLogic: 'AND',
208
+ rules: [{ fieldPath: 'company.employees', operator: 'gte', value: 50 }],
209
+ matchAction: 'continue',
210
+ noMatchAction: 'exclude',
211
211
  },
212
212
  {
213
213
  id: 'engagement',
214
214
  order: 1,
215
215
  name: 'High Engagement',
216
- filter_logic: 'AND',
217
- rules: [{ field_path: 'engagement_score', operator: 'gte', value: 7 }],
218
- match_action: 'tag_continue',
219
- no_match_action: 'continue',
220
- match_tags: ['high-engagement'],
216
+ filterLogic: 'AND',
217
+ rules: [{ fieldPath: 'engagement_score', operator: 'gte', value: 7 }],
218
+ matchAction: 'tag_continue',
219
+ noMatchAction: 'continue',
220
+ matchTags: ['high-engagement'],
221
221
  },
222
222
  {
223
223
  id: 'industry',
224
224
  order: 2,
225
225
  name: 'Target Industries',
226
- filter_logic: 'OR',
226
+ filterLogic: 'OR',
227
227
  rules: [
228
- { field_path: 'company.industry', operator: 'eq', value: 'Technology' },
229
- { field_path: 'company.industry', operator: 'eq', value: 'SaaS' },
230
- { field_path: 'company.industry', operator: 'eq', value: 'FinTech' },
228
+ { fieldPath: 'company.industry', operator: 'eq', value: 'Technology' },
229
+ { fieldPath: 'company.industry', operator: 'eq', value: 'SaaS' },
230
+ { fieldPath: 'company.industry', operator: 'eq', value: 'FinTech' },
231
231
  ],
232
- match_action: 'output',
233
- no_match_action: 'exclude',
232
+ matchAction: 'output',
233
+ noMatchAction: 'exclude',
234
234
  },
235
235
  ],
236
- created_at: new Date().toISOString(),
237
- updated_at: new Date().toISOString(),
236
+ createdAt: new Date().toISOString(),
237
+ updatedAt: new Date().toISOString(),
238
238
  };
239
239
 
240
240
  const sampleLeads = [